JVM指针压缩实现原理

JVM指针压缩实现原理

   概要

   Java 中的指针压缩(Pointer Compression)是一个与内存管理相关的优化技术,主要应用于 JVM 的对象引用(即指针)的存储方式。指针压缩的目标是减少对象引用占用的内存空间,从而提高内存利用效率,特别是在 64 位系统上。

   一、对象的内存布局

   在了解指针压缩之前,需要先搞懂java的实例对象在JVM虚拟机中内存结构是什么样的。

   在Hotspot虚拟机中,对象在内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

   1. 对象头(Header)

   对象头,又包括三部分:MarkWord、元数据指针、数组长度。

   64位系统下的对象头布局如下图:

 

   1)MarkWord

   用于存储对象运行时的数据,比如HashCode、锁状态标志、GC分代年龄等。这部分在64位操作系统下,占8字节(64bit),在32位操作系统下,占4字节(32bit)。

   2)指针

   对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

   这部分就涉及到一个指针压缩的概念,在开启指针压缩的情况下,占4字节(32bit),未开启情况下,占8字节(64bit),现在JVM在1.6之后,在64位操作系统下都是默认开启的。

   3)数组长度:这部分只有是数组对象才有,如果是非数组对象,就没这部分了,这部分占4字节(32bit)。

   2.  实例数据(Instance Data)

   用于存储对象中的各种类型的字段信息(包括从父类继承来的)。

   3.  对齐填充(Padding)

   这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于java对象的大小默认是按照8字节对齐,也就是说java对象的大小必须是8字节的倍数。如果算到最后不够8字节的话,那么就会进行对齐填充。

   如果对象头+对象体大小不是8字节的倍数,则通过该部分进行补齐,比如对象头+对象体大小只有30字节,则需要补齐到32字节,这里的对齐填充就是2字节。

   二、指针压缩

   1. 为什么要指针压缩?

   计算机操作系统分32位和64位,这里的位在计算机里是用0和1来表示的,用32个(或64个)二进制0和1的组合来表示内存地址。以32位为例,在普通的内存中,对象的大小最小是以1字节来计算的,通过0和1的排列组合,能够表示寻址的内存空间最大就是2^32个,换算成内存空间就是2 ^ 32 / 1024 / 1024 / 1024 = 4G,也就是说32位的操作系统最大能寻址的内存空间只有4G。同理,64位的操作系统最大能寻址的内存空间就更大了。

   但是当我们准备将32位系统切换到64位系统,起初我们可能会期望系统性能会立马得到提升,但现实情况可能并不是这样的,为什么呢?

   1)增加了GC开销

   64位对象引用需要占用更多的堆空间,留给其他数据的空间将会减少,从而加快了GC的发生,更频繁的进行GC。GC占用的CPU时间越多,那么我们的应用程序占用CPU的时间就越少,响应会变慢,吞吐量会降低。

   2)降低CPU缓存命中率

   64位对象引用增大了,CPU能缓存的对象将会更少,从而降低了CPU缓存的效率,增加了对内存的访问。CPU对CPU缓存的访问速度可比对内存的访问速度快太多了,所以大量的对内存访问,会降低CPU的执行效率,增加了执行时间,从而影响性能。

   分析:既然32位系统内存不够,64位内存够但又影响性能,那有没有折中方案来解决这两个问题呢,于是聪明的JVM开发者想到了利用压缩指针,在64位的操作系统中利用32位的对象指针引用获得超过4G的内存寻址空间。

   2. 如何压缩指针

  在 JVM 中,对象的内存地址总是 8 字节对齐,即每个对象的地址都是 8 的倍数,这意味着其二进制表示的末尾 3 位始终为 0。指针压缩正是利用了这一特点,省略了这些无意义的 0 位:

  1)压缩存储

  使用 32 位来存储指针(而非 64 位),既然JVM已经知道了这些对象的内存地址后三位始终是0,那么这些无意义的0就没必要在堆中继续存储。相反,我们可以利用存储0的这3位bit存储一些有意义的信息,这样我们就多出3位bit的寻址空间,也就是说如果我们继续使用32位来存储指针,只不过后三位原本用来存储0的bit现在被我们用来存放有意义的地址空间信息。

  2)地址还原

  寻址的时候,JVM将这32位的对象引用左移3位即可(后三位补0)。我们原本32位的内存寻址空间一下变成了35位,可寻址的内存空间变为2 ^ 35 / 1024 / 1024 / 1024 = 32G,也就是说在64位系统JVM的内存可扩大到32G了,基本上可满足大部分应用的使用了。

  所以在64位系统下,通过压缩指针我们可以继续使用32位来处理(引用指针由8字节可降低到4字节),存储的时候右移3位,寻址的时候左移3位。 

  如下图所示:

   这样一来,JVM虽然额外的执行了一些位运算但是极大的提高了寻址空间,并且将对象引用占用内存大小降低了一半,节省了大量空间,况且这些位运算对于CPU来说是非常容易且轻量的操作,可谓是一举两得。

   正是因为对象地址对齐至8的倍数,才会多出3位bit让我们储额外的地址信息,进而将4G的寻址空间提升至32G。

   这种方式看作是一种内存地址映射。java对象的指针地址就可以不用存对象的真实的64位地址了,而是可以存一个映射地址编号。

   3. JVM指针压缩参数

   关于压缩指针的两个参数:

   UseCompressedClassPointers:压缩类指针

   UseCompressedOops:压缩普通对象指针

   Oops是Ordinary object pointers的缩写,这两个参数默认是开启的,即-XX:+UseCompressedClassPointers,-XX:+UseCompressedOops,也可手动设置,

   如下所示:

-XX:+UseCompressedClassPointers //开启压缩类指针
-XX:-UseCompressedClassPointers //关闭压缩类指针
-XX:+UseCompressedOops  //开启压缩普通对象指针
-XX:-UseCompressedOops  //关闭压缩普通对象指针

   注意:32位HotSpot VM是不支持UseCompressedOops参数的,只有64位HotSpot VM才支持。Oracle JDK从6 update 23开始在64位系统上会默认开启压缩指针。

   三、总结

   1. 通过指针压缩,利用对齐填充特性,通过映射方式达到了内存地址扩展的效果

   2. 指针压缩能够节省内存空间,同时提高了程序的寻址效率

   3. 堆内存设置最好不要超过32GB,这时指针压缩将会失效,造成空间的浪费。

   原因:

   1)指针压缩依赖于堆内存的大小。为了实现指针压缩,JVM 会假定对象引用(指针)指向的内存地址空间最大为 32GB。因此,当堆内存设置超过 32GB 时,JVM 就无法再使用 4 字节来表示每个对象的地址了,因为地址空间已经超出了 32GB 的限制。

   2)当堆内存超过 32GB 时,JVM 会自动禁用指针压缩,因为指针压缩的优化只有在地址范围较小的时候才有意义。如果堆内存更大,指针的地址空间超出了压缩的范围,那么就没有办法继续使用 4 字节的指针了

   4. 指针压缩不仅可以作用于对象头的类型指针,还可以作用于引用类型的字段指针(包括引用类型的数组指针)

   5. -XX:ObjectAlignmentInBytes,默认是 8,也就是 8 字节对齐


参考链接:

https://blog.csdn.net/liujianyangbj/article/details/108049482
https://www.cnblogs.com/star95/p/17512212.html

posted @   欢乐豆123  阅读(83)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
历史上的今天:
2022-11-06 研发流程
点击右上角即可分享
微信分享提示