This is the first part of the JVM Inside Out series. For your convenience you can find other parts using the links below:
Part 1 — Getting object address
Part 2 — Reading object content
Part 3 — Java raw type trickery
Part 4 — Locks and out of band exceptions
How to get object address in JVM? There is actually one easy trick to do it via reflection. See this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package unsafe; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; public class Play { public static void main (String[] args) throws java.lang.Exception { AddressExtractor addressExtractor = new AddressExtractor(); Class<? extends AddressExtractor> clazz = addressExtractor.getClass(); Field field = clazz.getDeclaredField("pointerValue"); Field type = Field.class.getDeclaredField("type"); AccessibleObject.setAccessible(new AccessibleObject[]{field, type}, true); type.set(field, Object.class); Dummy foo = new Dummy(); foo.value = 0xBADF00D; field.set(addressExtractor, foo); System.out.println(Long.toHexString(addressExtractor.pointerValue)); System.out.println(field.get(addressExtractor) == foo); System.in.read(); } } class AddressExtractor { public long pointerValue; } class Dummy{ public long value; } |
We create an object of type AddressExtractor
which has one long field for storing the address. Next, we use reflection to get field named pointerValue
. Trick is that we can use reflection to examine Field
instance pointing to the pointerValue
field. Since this instance must know the type, it stores it in a field called type
. We can now use reflection to modify it and trick reflection to think that pointerValue
is of type Object
.
Next, in lines 16-18 we just create some dummy object to see if we are right. We initialize its field to some readable value and then assign this object to pointerValue
long field. JVM now needs to assign Ordinary Object Pointer to the long value. We can then print it out.
I’m using 64-bit JVM and disable OOP compression using -XX:-UseCompressedOops
. I get the following output:
1 2 |
12999a630 true |
Let’s attach WinDBG and see what happens:
1 2 3 |
0:019> dd 0x12999a630 00000001`2999a630 00000001 00000000 1b5a3bf8 00000000 00000001`2999a640 0badf00d 00000000 |
We can see first two integers being a mark-word, next two integers for klass word (remember, we disabled OOP compression). Finally, we get one long field (two integers) with expected value.
Of course, this address is valid as long as GC doesn’t move the object. Most likely, if you add System.gc()
at the end then the object will be moved.