JVM之压缩指针——Compressed oops

1、32位 vs. 64位

        32位与64位的对比是在2000年以后兴起的。然而64位CPU早就在超级计算机领域中得到应用了,只是最近几年64位CPU才在PC上成为主流配置。从32位到64位的转变,绝对不是一件简单的工作,因为几乎所有的东西,从硬件到操作系统都必须要发生改变。Java也是在这个改革的趋势中引入了64位的虚拟机。

        在32位到64位的转变中,人们最大的获益是内存容量。在一个32位的系统中,内存地址的宽度就是32位,这就意味着,我们最大能获取的内存空间是2^32(或者4 G)字节。这个容量明显不够用了!在一个64位的机器中,理论上,我们能获取到的内存容量是2^64字节,这是一个十分庞大的数字(ridiculously huge number)。可惜的是,这只是一个理论值,而现实中,因为有一堆有关硬件和软件的因素限制,我们能得到的内存要少得多。举个例了来说,Windows 7 Ultimate系统只能支持192GB的内存。可能许多人会说“192GB好大呀”,但是和2^64比起来,它真的挺小的,真的。好了,我们聊了半天,64位的重要性我们也知道了,那么接下来,我们就谈谈compressed oops能帮我们做什么。

        “天下没有免费的午餐”。我们在64位机器中能获取极大的内存容量也是有花费的。一般来说,一个应用跑在64位机上会花费更多的内存。而且如果不是那种可以乎略不记的小程序的话,这种花费可是不能被忽略不记的哦!而Compressed oops是在64位环境中使用32位类指针(class pointer),这样就可以帮助我们节省一些内存空间,但是要保证内存不大于32GB。接下来,我们细说说一个对象在Java里是如何表示的。

2、java中对象表示

我们先用一个小例子来帮助我们理解对象(objects)在Java中是如何表示的。我们先给一个Integer对象赋一个值。当你写出下面的这句代码时:Integer i = new Integer(23);

编译器实际上要在堆中给这个对象分配比32位多得多的空间。Java中整形数的值是32位,但是每个对象都含有一些“头部”内容。而这些头部内容在不同的VM中是不同,在32位与64位虚拟机中也是不同的。在32位的虚拟机中,头部的每个“域”(field)的长度是一个字长(4字节)。而在64位虚拟机中,整形数的值域长还是32位,但是其他域的长度却增加到了8个字节(64位机中的一个字长)。如果你以为就这点差别的话,我就呵呵了!对象在内存中是要按字对齐的,也就是说,在64位机中,一个对象所占的内存要能被64整除。而我们要做文章主要的地方就是在Hotspot虚拟机中被称为“Klass”的类指针(class pointer)。从下图中,我们可以看到的是,在一个正常的64位虚拟机中klass的长度是8字节,但是启动compressed oops后,它就变成了4字节了。

JVM之压缩指针——Compressed oops_第1张图片

3、什么是 OOP ?

在堆中,32位的对象引用(指针)占4个字节,而64位的对象引用占8个字节。也就是说,64位的对象引用大小是32位的2倍。64位JVM在支持更大堆的同时,由于对象引用变大却带来了性能问题:

  • 增加了GC开销:64位对象引用需要占用更多的堆空间,留给其他数据的空间将会减少,从而加快了GC的发生,更频繁的进行GC。
  • 降低CPU缓存命中率:64位对象引用增大了,CPU能缓存的oop将会更少,从而降低了CPU缓存的效率。

为了能够保持32位的性能,oop必须保留32位。那么,如何用32位oop来引用更大的堆内存呢?答案是——压缩指针(CompressedOops)。

OOP = “ordinary object pointer” 普通对象指针。 启用CompressOops后,会压缩的对象:

  • 每个Class的属性指针(静态成员变量)
  • 每个对象的属性指针
  • 普通对象数组的每个元素指针

当然,压缩也不是万能的,针对一些特殊类型的指针,JVM是不会优化的。 比如指向PermGen的Class对象指针,本地变量,堆栈元素,入参,返回值,NULL指针不会被压缩。

1)CompressedOops原理:

64位地址分为堆的基地址+偏移量,当堆内存<32GB时候,在压缩过程中,把偏移量/8后保存到32位地址。在解压再把32位地址放大8倍,所以启用CompressedOops的条件是堆内存要在4GB*8=32GB以内。

CompressedOops,可以让跑在64位平台下的JVM,不需要因为更宽的寻址,而付出Heap容量损失的代价。 不过它的实现方式是在机器码中植入压缩与解压指令,可能会给JVM增加额外的开销。

2)零基压缩优化(Zero Based Compressd Oops)

零基压缩是针对压解压动作的进一步优化。 它通过改变正常指针的随机地址分配特性,强制堆地址从零开始分配(需要OS支持),进一步提高了压解压效率。要启用零基压缩,你分配给JVM的内存大小必须控制在4G以上,32G以下。

3)总结:

  • 如果GC堆大小在4G以下,直接砍掉高32位,避免了编码解码过程;
  • 如果GC堆大小在4G以上32G以下,则启用UseCompressedOop;
  • 如果GC堆大小大于32G,压指失效,使用原来的64位(所以说服务器内存太大不好......)。

注:

32位HotSpot VM是不支持UseCompressedOops参数的,只有64位HotSpot VM才支持。

Oracle JDK从6 update 23开始在64位系统上会默认开启压缩指针。

你可能感兴趣的:(java)