我尝试将对象的klass指针更改为指向具有相同设置的其他类。确切地说,对于我的测试,我使用原始类的副本和修改后的toString()
方法,只是打印出其他内容。
假设JVM以相同的方式命令属性,两个类的内存对象应该看起来相同。
因此,在我的测试中,我从新类的对象中获取了klass指针,并设置了旧的原始类的对象。在调用toString()
之后,我按预期看到了新输出。
然而,当我在循环中执行此操作时,JVM崩溃了。我尝试创建new Test()
个对象,并将klass指针修改为指向Test2
,如下所示(注意:64位压缩OOP):
int test2KlassIdentifier = unsafe.getInt(test2Obj, 8L);
unsafe.putInt(testObj, 8L, test2KlassIdentifier);
创建了数十万个对象后,我得到了一个核心转储:
# 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
然后我将数字减少到只创建100.000 - >在我之后创建了一堆new Object()
之前,没有核心转储。
所以我的感觉是,这是一个与GC有关的问题,而且我的改变在内部混淆了一些东西。但是,我想了解我的“修补”对象与新创建的Test2
类型对象的不同之处
答案 0 :(得分:2)
不要试图欺骗JVM。这样的实验几乎总是注定要失败。
在这种特殊情况下,JIT编译器在偏移#8处拒绝'常规'加载操作,因为它假定只允许LoadKlassNode
在klass_offset读取。但是还有很多其他原因导致JVM崩溃。
即使这个技巧有时在解释代码中有效,但在JIT编译之后很可能会失败,因为对象类的概念不仅仅是对象头中的引用。如果您尝试在另一个实例上调用它,则为一个特定类生成的机器代码将变为无效:考虑指令流中的绝对地址等。
另外请注意,更改标题时类可能会出现不兼容的状态,例如:它们具有不同的常量池缓存状态以及JVM可能会延迟填充的其他结构。