I tried to change the klass pointer of an object to point to a different class which has an identical setup. To be precise, for my test I used a copy of the original class with a modified toString()
method, just to print out something else.
Assuming that the JVM orders the attributes in the same way in memory objects of the two classes should look identical.
So in my test I obtained the klass pointer from an object of the new class and set in on an object of the old, original class. After calling toString()
I saw the new output as expected.
When I did this in a loop, however, the JVM crashed. I tried to create new Test()
objects and modified the klass pointer to point to Test2
like this (note: 64bit compressed OOP):
int test2KlassIdentifier = unsafe.getInt(test2Obj, 8L);
unsafe.putInt(testObj, 8L, test2KlassIdentifier);
After creating hundreds of thousands of objects I got a core dump:
# Internal Error (C:\ojdkbuild\lookaside\java-1.8.0-openjdk\hotspot\src\share\vm\opto\memnode.cpp:906), pid=27120, tid=0x0000000000009374
# assert(!(adr_type->isa_oopptr() && adr_type->offset() == oopDesc::klass_offset_in_bytes())) failed: use LoadKlassNode instead
I then reduced the number to only create 100.000 --> no core dump until I created a bunch of new Object()
s afterwards.
So my feeling is that it is a GC related problem and that my change messes up something internally. I would like to understand, however, how my "patched" object is different from a newly created object of type Test2
Do not try to fool JVM. Such experiments are almost always doomed to failure.
In this particular case JIT compiler rejects a 'regular' load operation at offset #8, since it assumes that only LoadKlassNode
is allowed to read at klass_offset. But there are many other reasons why such tricks may crash JVM.
Even if the trick works sometimes in interpreted code, it is likely to fail after JIT compilation, since the notion of object's class is a lot more than just a reference in object header. The machine code generated for one particular class becomes invalid if you try to call it on a different instance: think of abolute addresses in the instruction stream etc.
Also mind that classes may have incompatible states when you change the header, e.g. they have different states of constant pool cache and possibly other structures that JVM fills in lazily.