危险代码:内存中的Java类和对象为何变得不安全—Part2

本文由 ImportNew - 吴功伟 翻译自 zeroturnaround。如需转载本文,请先参见文章末尾处的转载要求。

普通对象指针和压缩普通对象指针

堆中的Java对象使用普通对象指针(OOP)来表示的。OOP是指向Java堆内部某一内存地址的管理指针,在JVM处理器的虚地址空间中是一块单独的连续地址区域。

OOP通常和机器指针大小相同,在一个LP64的系统中就是64比特。在一个ILP32系统中,堆最大尺寸比40亿字节稍微少一点,足以满足大多数应用了。

译注:32位环境涉及”ILP32″数据模型,是因为C数据类型为32位的int、long、指针。而64位环境使用不同的数据模型,此时的long和指针已为64位,故称作”LP64″数据模型。

Java堆中的管理指针指向8字节对齐对象(https://wikis.oracle.com/display/HotSpotInternals/CompressedOops )。在大多数情况下,压缩普通对象指针代表了JVM中从64比特的堆基地址偏移32比特的对象管理指针。因为它们都是对象偏移而不是字节偏移,所以可以用于寻址40亿对象或者320亿字节的堆大小。使用时,必须乘以8并且加上Java堆的基地址去定位对象的位置。使用压缩普通对象指针的对象大小和ILP32系统差不多。

在Java v6u23及以后的版本中,支持并且默认启用压缩普通对象指针。在Java v7中,当没有指定”-Xmx”时,64比特
JVM处理器默认使用压缩普通对象指针;指定”-Xmx”时小于320亿字节。在6u23版本之前的JDK 6,在Java命令中使用”-XX:+UseCompressedOops”标志启用这一特性(http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html )。

在我们的示例中,通过“-XX:-UseCompressedOops” 关闭了压缩普通对象指针功能,所以64bit的指针大小就是8字节。

关于示例类的一些事

在这篇文章中,我们将使用一个示例类(SampleClass)展示对象地址恢复、列出字段布局等。这是一个简单类,包含了三个基本数据类型并且继承了SampleBaseClass,用来展示继承的内存布局。示例类的定义如下,示例代码可以在GitHub上找到:

publicfinal class SampleClass extendsSampleBaseClass {
 
    privatefinal static byte b = 100;
 
    privateint i = 5;
    privatelong l = 10;
 
    publicSampleClass() {
 
    }
 
    publicSampleClass(inti, longl) {
        this.i = i;
        this.l = l;
    }
 
    publicint getI() {
        returni;
    }
 
    publicvoid setI(inti) {
        this.i = i;
    }
 
    publiclong getL() {
        returnl;
    } 
    publicvoid setL(longl) {
        this.l = l;
    }
     publicstaticbytegetB() {
        returnb;
    }
}
publicclass SampleBaseClass {
 
    protectedshort s = 20;
}

要得到Java类的内存地址没有简便方法。为了得到地址,必须使用一些技巧并且做一些牺牲!本文会介绍两种获得Java类内存地址的办法。

方法一

在JVM中,每个对象都一个指向类的指针。但是只指向具体类,不支持接口或抽象类。如果我们得到一个对象的内存地址,就可以很容易地找到类的地址。这种方法对于那些可以创建实例的类来说非常有用。但是接口或抽象类不能使用这种方法。http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/9b0ca45cd756/src/share/vm/oops/oop.hpp

For 32 bit JVM:
	_mark	: 4 byte constant
	_klass	: 4 byte pointer to class 

For 64 bit JVM:
	_mark	: 8 byte constant
	_klass	: 8 byte pointer to class

For 64 bit JVM with compressed-oops:
	_mark	: 8 byte constant
	_klass	: 4 byte pointer to class

内存中对象的第二个字段(对32位JVM偏移是4,64位JVM偏移是8)指向了内存中的类定义。你可以使用“sun.misc.Unsafe” 类得到此偏移的内存值。这里用到的是在上一篇中提到的SampleClass

For 32 bit JVM:
	SampleClass sampleClassObject = new SampleClass();
	int addressOfSampleClass = unsafe.getInt(sampleClassObject, 4L);

For 64 bit JVM:
	SampleClass sampleClassObject = new SampleClass();
	long addressOfSampleClass = unsafe.getLong(sampleClassObject, 8L);

For 64 bit JVM with compressed-oops:
	SampleClass sampleClassObject = new SampleClass();
	long addressOfSampleClass = unsafe.getInt(sampleClassObject, 8L);

方法2

使用这种方法,可以得到任何类的内存地址(包括接口、注解、抽象类和枚举)。Java7中类定义的内存地址结构如下:32位JVM的地址偏移从第4到80字节,64位JVM的地址偏移从8字节到160字节,压缩普通对象指针的地址偏移从第4到84字节。

没有预先定义好的偏移,但是在类文件解析器中作为“隐藏”字段给出了注释(这里实际上有3个字段:classarrayClassresolvedConstructor)。因为在java.lang.Class中有18个非静态引用字段,他们只是恰好表示了这段偏移。

更多信息可以参见ClassFileParser::java_lang_Class_fix_pre() 和JavaClasses::check_offsets()。文档地址:http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/9b0ca45cd756/src/share/vm/classfile/.

获取内存地址的示例代码如下:

For 32 bit JVM:
	int addressOfSampleClass = unsafe.getInt(SampleClass.class, 80L);

For 64 bit JVM:
	long addressOfSampleClass = unsafe.getLong(SampleClass.class, 160L);

For 64 bit JVM with compressed-oops:
	long addressOfSampleClass = unsafe.getInt(SampleClass.class, 84L);

下一篇会讲解如何得到内存对象地址,敬请期待。

原文链接:  zeroturnaround  翻译:  ImportNew.com  吴功伟
译文链接:  http://www.importnew.com/8488.html
转载请保留原文出处、译者和译文链接。 ]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值