Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Object.toString() get the "memory address" and how can I mimic it

The toString method of Object is unique in that it seems to be the only place in Java where the memory address is viewable. How does Object do this?

I would like to know so that I can mimic its implementation in a class of my own. I can't use super.toString() because I'm extending a class which overrides toString already.

Update: The premise of my question asks for the memory address, but the answers have indicated that this premise is incorrect, so what I am actually asking is: How does Object.toString() return what it does, and how can I mimic it?

like image 518
4castle Avatar asked Apr 02 '16 17:04

4castle


3 Answers

It is not the memory address, it is the hashCode(). See also Object.toString() which says (in part)

The toString method for class Object returns a string consisting of the name of the class of which the object is an instance, the at-sign character @, and the unsigned hexadecimal representation of the hash code of the object. In other words, this method returns a string equal to the value of:

getClass().getName() + '@' + Integer.toHexString(hashCode())

And Object.hashCode() says (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.) so it's not required to be a memory address, and if used it's only visible to the internal (native) implementation of Object.

like image 61
Elliott Frisch Avatar answered Oct 11 '22 15:10

Elliott Frisch


The toString method of Object is unique in that it seems to be the only place in Java where the memory address is viewable. How does Object do this?

It doesn't get the address, in the HotSpot JVM, it gets a randomly generated 31-bit hashcode stored in the header of the object. This has to be stored because;

  • the hashcode cannot change even if the object is moved and has a new address.
  • the address is not random enough. The lower 8-bits of the address are always 0. After every GC, the first object to be creates is always the same.
  • the address could be 64-bit.

DO TRY THIS AT HOME, NOT SUITABLE FOR WORK!!.

You can get/set the hashCode() using Unsafe

static final Unsafe UNSAFE;

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

public static void setIdentityHashCode(Object o, int code) {
    UNSAFE.putInt(o, 1l, code & 0x7FFF_FFF);
}

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    Double d = 1.0;
    Double d2 = 1.0;
    setIdentityHashCode(d, 1);
    setIdentityHashCode(d2, 1);
    System.out.println("d: "+d+" System.identityHashCode(d): "+System.identityHashCode(d));
    System.out.println("d2: "+d2+" System.identityHashCode(d2): "+System.identityHashCode(d2));
    System.out.println("d == d2: " + (d == d2));
}

prints

d: 1.0 System.identityHashCode(d): 1
d2: 1.0 System.identityHashCode(d2): 1
d == d2: false

You can get the address from the reference value provided you know how the memory has been translated. In the simplest case, (where you have 64-bit references) the reference is untranslated and the address is the value stored in the reference.

If you run this on a 64-bit JVM with -XX:-UseCompressedOops

// This only works if a GC doesn't move the object while attempting to access it.
static final Unsafe UNSAFE;

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

// run with: -ea -XX:-UseCompressedOops
public static void main(String[] args) {
    Object i = 0x12345678;
    System.out.printf("indentityHashCode = %08x%n", System.identityHashCode(i));

    Object[] obj = { i };
    assert Unsafe.ARRAY_OBJECT_INDEX_SCALE == 8; // 8 bytes per reference.
    long address = UNSAFE.getLong(obj, (long) Unsafe.ARRAY_OBJECT_BASE_OFFSET);
    System.out.printf("%x%n", address);
    for (int j=0;j<24;j++)
        System.out.printf("%02x ", UNSAFE.getByte(address + j) & 0xFF);
    System.out.println();
    // now some really scary sh!t
    UNSAFE.putLong(i, 8L, UNSAFE.getLong(0L, 8L));
    System.out.printf("`i` is now a %s and is %x%n", i.getClass(), i);
}

prints

indentityHashCode = 5a07e868
7fbf41cb8560
01 68 e8 07 5a 00 00 00 48 33 3f b9 b9 7f 00 00 78 56 34 12 00 00 00 00 
   ^^hashCode^          ^class address  ^       ^int value^
`i` is now a class java.lang.Long and is 12345678
like image 38
Peter Lawrey Avatar answered Oct 11 '22 15:10

Peter Lawrey


It doesn't. What it gets is the hash code, not the memory address, and the hash code has no necessary connection to the memory address. (It might in some implementations.)

In the base implementation, that's System.identityHashCode, though it still has no relationship to the memory address.

like image 44
Louis Wasserman Avatar answered Oct 11 '22 13:10

Louis Wasserman