压缩指针:64位系统下,Java虚拟机是如何“偷”回4字节内存的?

Java对象:在内存中的真面目

在Java中,通过new关键字创建一个Java类的实例对象时,该对象会通过碰撞指针方式存储在内存的堆中,并被分配一个内存地址。在Java虚拟机中,一个Java对象由对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)三部分构成。

对象头

对象头由两个字(计算机术语,表示计算机处理数据的最小单位)组成。如果对象是一个Java数组,对象头中还必须包含一部分用于记录数组长度的数据,因为虽然Java虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但无法从数组的元数据中确定数组的大小。

对象头的两个字分别是Mark Word和Klass Pointer。

1)Mark Word:即标记字段,用于存储对象自身的运行时数据,如哈希码(HashCode)、垃圾回收分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。

2)Klass Pointer:即类型指针,是对象指向它的类元数据的指针,Java虚拟机通过这个指针来确定这个对象是哪个Java类的实例。

实例数据

实例数据部分存储对象的属性字段信息。如果对象没有属性字段,那么这部分就不会有数据。字段类型的不同会占用不同的字节,例如,boolean类型占1个字节,int类型占4个字节等。

对齐填充

对齐填充是为了满足Java虚拟机堆中对象起始地址需要对齐至8的倍数的要求。如果一个对象未使用到8N个字节,则需要进行填充,以补齐对象头和实例数据占用内存后的剩余空间。

字段内存对齐的目的之一是确保字段只出现在同一处理器的缓存行中。如果字段未对齐,可能会出现跨缓存行的字段,即该字段的读取可能需要替换两个缓存行,而该字段的存储也可能同时污染两个缓存行,这对程序执行效率都是不利的。实际上,对齐填充的最终目标是为了实现计算机的高效寻址。

压缩指针

在64位Java虚拟机中,对象头部的Mark Word和Klass Pointer,分别占据64位,因此每个Java对象的内存额外开销就是16字节。以Integer类为例,它仅有一个int类型的私有字段,占用4字节,因此,每个Integer对象的内存开销至少增加400%。这也是Java引入基本数据类型(Primitive Data Type)的原因之一。

在64位系统中,普通的对象引用通常需要64位(8字节)的空间。然而,对于许多应用程序,这种大尺寸的引用是不必要的,因为它们的堆内存使用量远小于64位地址空间的上限(即18亿GB)。因此,64位Java虚拟机引入了压缩指针(Compressed Pointer)技术,将Java对象指针压缩为32位。这样,对象头部中的Klass Pointer也被压缩为32位,从而将对象头部的大小从16字节减小到12字节。

工作原理

Java虚拟机假设所有对象的大小都是8字节的倍数(不足将对齐填充),因此对象的实际地址可以表示为基地址加上一个偏移量,而这个偏移量是8的倍数。因此,Java虚拟机只需要存储这个偏移量,而不是完整的64位地址。由于偏移量是8的倍数,所以它的最后三位总是0,Java虚拟机可以将这个偏移量右移三位,从而将其压缩到32位的空间。

下面是8和它的倍数对应的二进制关系。

c 复制代码
8 = 1000
16 = 10000
24 = 11000
32 = 100000
40 = 101000
48 = 110000
56 = 111000
64 = 1000000
72 = 1001000

可以看到,在二进制下,8和它的倍数的后三位都是0。因为后三位都是0,所以可以在拿到地址后舍弃后三位,读取的时候加上后三位。这个过程可以用以下的伪代码表示。

int offset = getObjectOffset(); // 获取对象的32位偏移量

offset = offset << 3; // 将偏移量左移三位

long address = HEAP_BASE + offset; // HEAP_BASE是堆基地址,加上偏移量就可以得到对象的实际内存地址。

需要注意的是,压缩指针技术只适用于堆内存大小小于32GB的情况。如果堆内存大小超过32GB,那么32位的偏移量将不足以表示所有可能的对象位置,因此必须使用完整的64位引用。

未完待续

很高兴与你相遇!如果你喜欢本文内容,记得关注哦

相关推荐
poemyang2 天前
为什么Java/Python程序无需关心内存释放?揭秘垃圾回收(GC)的核心概念
java虚拟机·垃圾回收
poemyang3 个月前
性能优化之母:为什么说“方法内联”是编译器优化中最关键的一步棋?
java虚拟机·编译原理·即时编译器·方法内联
poemyang3 个月前
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
java虚拟机·编译原理·逃逸分析·即时编译器
poemyang3 个月前
解锁硬件潜能:Java向量化计算,性能飙升W倍!
java虚拟机·编译原理·jit·向量化计算·smid
poemyang3 个月前
Java编译器优化秘籍:字节码背后的IR魔法与常见技巧
java虚拟机·编译原理·ir·即时编译器
poemyang3 个月前
“代码跑着跑着,就变快了?”——揭秘Java性能幕后引擎:即时编译器
java·java虚拟机·编译原理·jit·即时编译器
poemyang3 个月前
“同声传译”还是“全文翻译”?为何HotSpot虚拟机仍要保留解释器?
java·java虚拟机·aot·编译原理·解释执行
poemyang3 个月前
a+b=c,处理器一步搞定,Java虚拟机为啥要四步?
java·java虚拟机·java字节码
poemyang3 个月前
Hello World背后藏着什么秘密?一行代码看懂Java的“跨平台”魔法
java·java虚拟机·编译原理·java字节码