1. 什么是JVM的指针压缩?
1.1 前期准备
用的是JDK8
验证代码,使用org.openjdk.jol工具打印的对象布局信息
1.2 开启指针压缩
HotSpot JDK8 默认开启指针压缩(一个User实例占用16bytes)
对象头 (Object Header)前8byte是mark word;中间4byte是Class Pointer; 后4byte是属性name
1.3 关闭指针压缩
添加JVM参数“-XX:-UseCompressedOops -XX:-UseCompressedClassPointers ”, 关闭指针压缩(一个User实例占用24bytes)
对象头 (Object Header)前8byte是mark word;中间8byte是Class Pointer; 后8byte是属性name
1.4 总结
对于同个一个User实例,在开启指针压缩后减少8bytes(24-16),这就是JVM中指的指针压缩,可以较少JVM上的堆空间占用。
2. 为什么需要JVM的指针压缩?
2.1 指针压缩背景
32位JVM的寻址空间只有2^32(4G),也就是说你的进程最大只能使用4G堆空间。并且由于还存在其他的一些限制,比如说swap空间、内核空间占用、内存碎片等等,实际上jvm可利用的空间要远小于4G,在很多场景下4G空间明显不够用,这就导致64位JVM的发展。
64位JVM,寻址空间最大是2^64,几乎就是无限大了。
2.2 指针压缩原因
因为64位JVM的每一个native指针都占用8个字节,而在32位JVM中只有4字节,加载这些额外的字节会影响内存的占用,且当内存增大了之后,GC停顿时间自然而然的也会跟着变大,因为内存中有更多的垃圾需要来回收了。
64位JVM,提供了更大的寻址空间,也引发内存占用更大,GC停顿时间变大的问题,指针压缩技术就应运而生。
3. 怎么实现JVM的指针压缩?
3.1 原理
官方提到,在 64bits 操作系统上实现 32bits 操作系统一样的指针管理方式,内存可以实现从 4g 到 32g 的提升。
指针压缩有两个过程,分别是编码和解码。编码时候将内存地址右移三位,解码时将内存地址左移三位,解码是在操作内存前,把原来地址左移三位,得到真实的内存地址。编码是指获得到内存地址后,右移三位,得到压缩后的内存地址。
至于为什么要对内存地址进行左右位移操作,是因为内存进行 8bytes 边界对齐后,内存地址的后三位都是 0,根据这个规则,就可以在存储的时候舍弃后面的三位,读取的时候再加上这三位。
3.2 为什么当Xmx大于32G时候,开启指针压缩的参数会失效?
实例如下,设置Xmx=33G, 默认开启指针压缩失效
因为JVM的指针压缩原理采用了8bytes边界对齐和3bit移位算法,所以最大的寻找空间是32G左右。如果指定的空间超过32G,JVM只能退回到8bytes的地址,才能提供超过32G的寻址空间。因此,建议在64位系统下,JAVA的堆内存设置最好不要超过32G,以避免指针压缩失效,触发GC的频次变高,以及造成空间浪费的情况出现。
在实际使用中,也需要注意一些细节问题。例如,需要根据实际业务负载情况和硬件性能来进行合理的内存设置,以达到最优化的效果。
4. 参考资料链接
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明