【问题标题】:Is there a way to get a reference address? [duplicate]有没有办法获得参考地址? [复制]
【发布时间】:2012-02-07 20:40:44
【问题描述】:

在Java中,有没有办法获取参考地址,比如说

String s = "hello"

我能得到s本身的地址吗?另外,我能得到引用所指的对象的地址吗?

【问题讨论】:

  • 请说明地址是什么意思?内存地址?垃圾收集器 ID?
  • 内存地址或垃圾回收器ID
  • 要么回到“C”的黑暗面,那里的内存地址困扰着你的每一个想法,或者回到光明的一面,那里既不需要也不想要内存地址。选择你的道路!
  • 你真的不应该这样做,这是下面大多数人所说的。彼得劳里表明你可以做到(他打败了我才提到它)。在这个意义上,其他人是“错误的”。但是,该类被称为“不安全”是有原因的。另一个原因是,这是一个 Sun/Oracle 类,不必由其他 JVM 实现者实现,因此如果您不知道自己在哪里运行,就不能在代码中依赖它。
  • 在调试中使用它有一些正当的理由,例如通过地址记录和堆转储来跟踪内存泄漏。还跟踪涉及缓存的性能问题,例如虚假共享。

标签: java memory jvm unsafe


【解决方案1】:

不,你不能。即使使用 Java Native Interface (JNI),您也只能获得数据结构的不透明句柄,而不是指向真实 JVM 对象的指针。

你为什么想要这样的东西?无论如何,它不一定是您可以用于任何事情的形式,即使来自本机代码。

【讨论】:

  • 只是想确保一些参考是相同的。
  • 如果你想比较两个参考,你可以用 == 来做。您无需获取对象的地址。
【解决方案2】:

其实用sun.misc.Unsafe可以获取地址但是真的很不安全。 GC 经常移动对象。

【讨论】:

  • 我在对 Peter Lawry 的评论中发布的链接显示了您如何做到这一点,这样您就可以保留正确的地址(因为如果您使用内存分配创建对象,您可能在技术上可以在堆之外,GC不会影响你)。我猜这就是 Terracotta 的 BigMemory 的工作原理。
【解决方案3】:

您可以使用 Unsafe 获取对象索引。根据 JVM 使用内存的方式(32 位地址、32 位索引、带偏移量的 32 位索引、64 位地址)会影响对象索引的有用程度。

这是一个假设您在 64 位 JVM 中有 32 位索引的程序。

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;

public class OrderOfObjectsAfterGCMain {
    static final Unsafe unsafe = getUnsafe();
    static final boolean is64bit = true; // auto detect if possible.

    public static void main(String... args) {
        Double[] ascending = new Double[16];
        for(int i=0;i<ascending.length;i++)
            ascending[i] = (double) i;

        Double[] descending = new Double[16];
        for(int i=descending.length-1; i>=0; i--)
            descending[i] = (double) i;

        Double[] shuffled = new Double[16];
        for(int i=0;i<shuffled.length;i++)
            shuffled[i] = (double) i;
        Collections.shuffle(Arrays.asList(shuffled));

        System.out.println("Before GC");
        printAddresses("ascending", ascending);
        printAddresses("descending", descending);
        printAddresses("shuffled", shuffled);

        System.gc();
        System.out.println("\nAfter GC");
        printAddresses("ascending", ascending);
        printAddresses("descending", descending);
        printAddresses("shuffled", shuffled);

        System.gc();
        System.out.println("\nAfter GC 2");
        printAddresses("ascending", ascending);
        printAddresses("descending", descending);
        printAddresses("shuffled", shuffled);
    }

    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();
    }

    private static Unsafe getUnsafe() {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

在 Java 6 update 26(带有压缩 oops 的 64 位)和 Java 7 上运行。注意:地址和相对地址以十六进制表示。

Before GC
ascending: 0x782322b20, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18
descending: 0x782322e58, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18
shuffled: 0x782322ec0, +78, -30, +90, -c0, +18, +90, +a8, -30, -d8, +f0, -30, -90, +60, -48, +60

After GC
ascending: 0x686811590, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18
descending: 0x686811410, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18
shuffled: 0x686811290, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18

或者有时

Before GC
ascending: 0x782322b20, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18, +18
descending: 0x782322e58, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, -18
shuffled: 0x782323028, -168, +150, -d8, -30, +60, +18, +30, +30, +18, -108, +30, -48, +78, +78, -30

After GC
ascending: 0x6868143c8, +4db0, +7120, -bd90, +bda8, -bd90, +4d40, +18, +18, -12710, +18, +80, +18, +ffa8, +220, +6b40
descending: 0x68681d968, +18, +d0, +e0, -165d0, +a8, +fea8, +c110, -5230, -d658, +6bd0, +be10, +1b8, +75e0, -19f68, +19f80
shuffled: 0x686823938, -129d8, +129f0, -17860, +4e88, +19fe8, -1ee58, +18, +18, +bb00, +6a78, -d648, -4e18, +4e40, +133e0, -c770

【讨论】:

  • ++ 干得好。本来打算从另一个 SO 链接中指出这一点:stackoverflow.com/questions/5574241/…,但你展示了如何用代码做到这一点,效果更好。
  • 这是很简洁的东西,但读者应该注意这不是可移植的; Unsafe 类是 Sun 派生的 JVM 的未记录实现细节。此代码不适用于 J9、JRockit、Dalvik 等。
  • @PeterLawrey 有没有关于 32 位和 64 位的东西的详细解释?我对此很感兴趣。
  • @dawnstar 这是一个很好的链接wikis.oracle.com/display/HotSpotInternals/CompressedOops
【解决方案4】:

在 Java 中无法获取对象的引用地址,例如您的字符串。 在 Java 中,对象的引用地址对用户是隐藏的。

在 C 中,您可以通过指针的概念来做到这一点。 Java在底层也有类似的概念,这就是引用地址。引用就像一个 C 指针,但它不是显式的。在 C 中,您可以通过 * 进行指针引用操作,但在 Java 中,这是不可能的。

我不太喜欢 C 语言,还因为在我看来,指针不是一个易于管理的概念。这也是我喜欢Java的原因之一,因为程序员不需要担心对象的指针。

就像@jarnbjo 所说,如果某些引用相似,您可以使用如下语法检查:

String s = "hello";
String g = s;
System.out.println("Are the reference addresses similar? "+(s==g));
g = "goodbye";
System.out.println("Are the reference addresses similar? "+(s==g));

注意== 检查引用地址的相等性。如果要检查字符串值的相等性,请使用equals() 方法。

我建议你阅读this SO question、this 维基百科页面和this 页面。

【讨论】:

    【解决方案5】:

    是的,你可以用 Unsafe 做到这一点,尽管它不是那么直接。 把对象或者实例引用放到一个int[]中,就可以了。 long[] 也应该没问题。

        @Test
    public void test1() throws Exception {
        Unsafe unsafe = Util.unsafe;
        int base = unsafe.arrayBaseOffset(int[].class);
        int scale = unsafe.arrayIndexScale(int[].class);
        int shift = 31 - Integer.numberOfLeadingZeros(scale);
        System.out.printf("base: %s, scale %s, shift: %s\n", base, scale, shift);
        base = unsafe.arrayBaseOffset(Object[].class);
        scale = unsafe.arrayIndexScale(Object[].class);
        shift = 31 - Integer.numberOfLeadingZeros(scale);
        System.out.printf("base: %s, scale %s, shift: %s\n", base, scale, shift);
        int[] ints = { 1, 2, 0 };
        String string = "abc";
        System.out.printf("string: id: %X, hash: %s\n", System.identityHashCode(string), string.hashCode());
        unsafe.putObject(ints, offset(2, shift, base), string);
        System.out.printf("ints: %s, %X\n", Arrays.toString(ints), ints[2]);
        Object o = unsafe.getObject(ints, offset(2, shift, base));
        System.out.printf("ints: %s\n", o);
        assertSame(string, o);
    
        Object[] oa = { 1, 2, string };
        o = unsafe.getObject(oa, offset(2, shift, base));
        assertSame(string, o);
        int id = unsafe.getInt(oa, offset(2, shift, base));
        System.out.printf("id=%X\n", id);
    }
    
    public static long offset(int index, int shift, int base) {
        return ((long) index << shift) + base;
    }
    

    【讨论】:

    • 这些类的包是什么?
    • sun.misc.Unsafe。 hg openjdk.java.net。 jdk\src\share\classes\sun\misc\Unsafe.java
    猜你喜欢
    • 2020-05-01
    • 2012-08-31
    • 2019-05-13
    • 2021-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    • 2017-08-22
    相关资源
    最近更新 更多