Java中变量的内存地址

171
请看下面的图片。 当我们使用Java中的new关键字创建一个对象时,我们从操作系统获取一个内存地址。
当我们写out.println(objName)时,我们可以看到一个“特殊”的字符串作为输出。我的问题是:
  1. 这个输出是什么?
  2. 如果它是操作系统给我们的内存地址:

    a)我如何将此字符串转换为二进制?

    b)我如何获取一个整数变量的地址?

alt text


6
好的,我会尽力进行翻译。针对您提供的内容,我的翻译如下:我没有投反对票,因为问题已经足够清晰明确了,只是建议您把问题描述成文字形式,这样人们就可以搜索到它。 - phunehehe
2
使用sun.misc.Unsafe可以获得Java对象的地址。有关程序清单,请参考: http://javapapers.com/core-java/address-of-a-java-object/ - Joseph
指向的值是a1和a2对象哈希码的十六进制表示。 - Naveen
9个回答

206

这里的类名和System.identityHashCode()由'@'字符分隔。身份哈希码表示的内容是特定于实现的。它通常是对象的初始内存地址,但是对象可能会被虚拟机在内存中移动。因此(简要地说),您不能依赖于它代表任何东西。

在Java中获取变量的内存地址是无意义的,因为JVM可以自由地实现对象并根据需要移动它们(在垃圾收集期间,您的对象可能会移动等等)

Integer.toBinaryString()将以二进制形式返回一个整数。


42
另一个有趣的点是,身份哈希码并不能保证是唯一的。例如,在64位JVM上,有2^32个身份哈希码,但有2^64个内存地址。 - Alex Jasmin
16
实际上,身份哈希码是不能更改的,否则会违反hashCode()的约定。 - Matt McHenry
3
我将用于记录/调试,以确定日志中的对象是否指向相同的对象而不是等效的对象。 identityHashcode 对于这些目的并非毫无意义,只是不是百分之百可靠的。 :) - Sled
1
@VedPrakash 对象哈希码允许对象存储在散列集合中。如果您想区分两个不同的对象,可以简单地使用引用相等性。 - Brian Agnew
1
在Eclipse内存分析器中查找对象的内存地址并不是毫无意义的。 - 99Sono
显示剩余4条评论

48

可以使用sun.misc.Unsafe:请参见@Peter Lawrey的这个很棒的答案-> 有没有一种方法可以获取引用地址?

使用其printAddresses()代码:

    public static void printAddresses(String label, Object... objects) {
    System.out.print(label + ": 0x");
    long last = 0;
    int offset = unsafe.arrayBaseOffset(objects.getClass());
    int scale = unsafe.arrayIndexScale(objects.getClass());
    switch (scale) {
    case 4:
        long factor = is64bit ? 8 : 1;
        final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
        System.out.print(Long.toHexString(i1));
        last = i1;
        for (int i = 1; i < objects.length; i++) {
            final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
            if (i2 > last)
                System.out.print(", +" + Long.toHexString(i2 - last));
            else
                System.out.print(", -" + Long.toHexString( last - i2));
            last = i2;
        }
        break;
    case 8:
        throw new AssertionError("Not supported");
    }
    System.out.println();
}

我设置了这个测试:

    //hashcode
    System.out.println("Hashcode :       "+myObject.hashCode());
    System.out.println("Hashcode :       "+System.identityHashCode(myObject));
    System.out.println("Hashcode (HEX) : "+Integer.toHexString(myObject.hashCode()));

    //toString
    System.out.println("toString :       "+String.valueOf(myObject));

    printAddresses("Address", myObject);

这是输出结果:

Hashcode :       125665513
Hashcode :       125665513
Hashcode (HEX) : 77d80e9
toString :       java.lang.Object@77d80e9
Address: 0x7aae62270

结论:

  • 哈希码(hashcode) ≠ 内存地址(address)
  • toString 方法输出的格式为:类名@16进制哈希码(hashcode)

整个堆的最大限制是32GB吗? - Paul Stelian

14

这是Object类的“toString()”方法的输出结果。如果你的类重写了toString()方法,它将打印完全不同的内容。


13

这不是内存地址,而是类名@哈希码
这是Object.toString()的默认实现。

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

where

类名 = 全限定名或绝对名(即包名后跟类名) 哈希码 = 十六进制格式(System.identityHashCode(obj) 或 obj.hashCode() 将以十进制格式给出哈希码)。

提示:
混淆的根本原因是 Object.hashCode() 的默认实现将对象的内部地址转换为整数

这通常通过将对象的内部地址转换为整数来实现,但这种实现技术不是 Java™ 编程语言所必需的。

当然,一些类可以覆盖默认实现,无论是 toString() 还是 hashCode()

如果您需要覆盖其 hashcode() 方法的对象的默认实现值,
您可以使用以下方法:System.identityHashCode(Object x)


9

正如Sunil所说,这不是内存地址,这只是哈希码

要获取相同的@内容,你可以:

如果该类中没有覆盖hashCode方法:

"@" + Integer.toHexString(obj.hashCode())

如果 hashCode 被重写,你可以使用以下方法获取原始值:
"@" + Integer.toHexString(System.identityHashCode(obj)) 

这经常会与内存地址混淆,因为如果您不覆盖hashCode(),则使用内存地址来计算哈希值。


2
您所得到的是Object类的toString()方法的结果,更确切地说是identityHashCode(),正如uzay95所指出的那样。
当我们使用new关键字在Java中创建对象时,我们从操作系统获取一个内存地址。
重要的是要认识到,在Java中,您所做的一切都由Java虚拟机处理。这是JVM提供的信息。实际上,在主机操作系统的RAM中发生的事情完全取决于JRE的实现。

0

0
在Java中,当你从一个类中创建一个对象,例如Person p = new Person();p实际上是指向类型为Person的内存位置的地址。
当使用语句打印p时,你会看到一个地址。关键字new会创建一个新的内存位置,其中包含class Person中包括的所有实例变量和方法,而p是指向该内存位置的引用变量。

在您的图片中,a1和a2是两个不同的内存地址。这就是获取两个不同值的原因。 - Panduka Wedisinghe

0
根据 Javadoc,打印对象引用时,它会返回对象的字符串表示形式,因为在内部它会调用 Object 类的 toString() 方法。输入图片说明

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接