JDK 15的新功能ZGC机制改进介绍 -malloc


在JDK 15中,ZGC可以投入生产了。换句话说,它现在是JDK中的一项产品(非实验性)功能,建议您在生产中使用它。这个变化是通过JEP 377引入的,是许多人多年努力的结晶
自从JDK 11首次引入以来,ZGC在以下版本中获得了许多新功能,稳定的性能和稳定性改进流以及对所有常用平台的支持。它还经历了很多测试。
总而言之,ZGC现在是稳定,高性能,低延迟的GC,可以随时处理您的生产工作负载。
 
改进了分配并发
分配Java对象通常非常快。确切地说,如何分配新对象取决于您所使用的GC。在ZGC中,分配路径经过许多层。第一层可以满足绝大多数分配,这是非常快的。而一直实现很少的分配直到最后一层则要慢得多。

仅当所有先前的层都不能满足分配时,分配才会在最后一层中结束。这是不得已的做法,ZGC将要求操作系统提交更多内存以扩展堆。如果仍然失败,或者已经达到最大堆大小(-Xmx),则将抛出OutOfMemoryError。
在JDK 15之前,ZGC在提交(和取消提交)内存时持有全局锁。当然,这意味着在任何给定时间只有一个线程可以扩展(或收缩)堆。提交和取消提交内存也是相对昂贵的操作,需要花费一些时间才能完成。结果,这种全局锁定有时成为争论的焦点。
在JDK 15中,重新分配了分配路径的这一部分,以便在提交和取消提交内存时不再保留此锁。结果,降低了在最后一层进行分配的平均成本,并且显着提高了该层处理并发分配的能力。
 
增量的取消提交
ZGC的取消提交功能最初是在JDK 13中引入的。该机制允许ZGC取消提交未使用的内存以缩小堆,并将未使用的内存返回给操作系统以供其他进程使用。为了使内存有资格取消提交,它必须已经闲置了一段时间(默认为300秒,由来控制-XX:ZUncommitDelay=<seconds>)。如果稍后需要更多内存,则ZGC将提交新内存以再次增大堆。
取消提交内存是一项相对昂贵的操作,完成此操作所需的时间往往会随着您正在操作的内存大小而扩展。在JDK 15之前,ZGC是否发现有2MB或2TB的内存适合取消提交并不重要,它仍然只向操作系统发出一个取消提交操作。事实证明,这是潜在的问题,因为取消分配大量内存(例如数百GB或TB级)可能要花费一些时间。在此期间,内存压力可能会发生巨大变化,但是ZGC无法途中中止或修改未提交操作。如果内存压力增加,ZGC首先必须等待任何正在进行的未提交操作完成,然后立即再次提交该内存中的一些。
JDK 15中对uncommit机制进行了重新设计,以逐步取消对内存的提交。ZGC现在将向操作系统发出许多较小的取消提交操作,而不是单个取消提交操作。这样可以迅速检测到内存压力的变化,并取消中止或修改未提交的进程。
 
增强了NUMA意识
ZGC 在Linux上一直是NUMA意识的,在某种意义上,当Java线程分配一个对象时,该对象将最终存储在Java线程所执行的CPU本地的内存中。在NUMA计算机上,访问CPU本地的内存会降低 内存延迟,从而提高整体性能。但是,只有在使用大页面(-XX:+UseLargePages)时,ZGC的NUMA意识才得以充分发挥。这在JDK 15中得到了解决,并且无论是否使用大页面,ZGC的NUMA意识现在始终充斥着它的全部荣耀。
  
JFR事件
以下JFR事件已添加或不再标记为实验性。

  • ZAllocationStall:如果Java线程遇到分配停顿,则生成。
  • ZPageAllocation:每次分配新的ZPage(堆区域)时生成。
  • ZRelocationSet& ZRelocationSetGroup:生成每个GC周期,并描述压缩/回收堆的哪些部分。
  • ZUncommit:每次ZGC取消提交Java堆的某些未使用部分时生成,即,将未使用的内存返回给操作系统。
  • ZUnmap:每次ZGC取消映射内存时生成。当需要将一组分散的页面重新映射为较大的连续页面时,ZGC将异步取消映射内存。

 
NVRAM上的Java堆
在过去的几年中,NVRAM领域的进步极大地提高了此类内存的速度,并且价格便宜得多。在某些环境和某些类型的应用程序中,将整个Java堆放在NVRAM(而不是RAM)上实际上是一个有吸引力的选择,您可以在其中用一些性能来换取便宜的内存。实际上,自JDK 10开始,HotSpot中的所有GC(ZGC除外)都对此-XX:AllocateHeapAt选项提供了支持 。但是,从JDK 15开始,ZGC现在也支持此功能。
 
单独使用压缩类指针
在HotSpot中,所有Java对象都有一个包含两个字段的标头,一个标记字和一个类指针。在64位CPU上,这两个字段通常都是64位宽,其中类指针是指向描述对象类(类型信息,vtable等)的内存的普通指针。压缩类指针Compressed Class Pointer特征(-XX:+UseCompressedClassPointers)有助于通过减少所有对象报头的大小减少整体堆的使用情况。它通过将类指针 字段压缩为32位(而不是64位)来实现。压缩类指针不是普通指针,而是到压缩类空间的偏移量,它具有已知的基地址。检索真实类的指针的JVM只是添加(可能比特移位)压缩类指针到的基地址的压缩空间类。
压缩类指针Compressed Class Pointer功能历来被拴在Compressed Oops功能上,这意味着你无法在启用压缩类指针Compressed Class Pointer的同时,必须同时启用Compressed Oops功能,这只是人为的依赖,因为没有技术原因导致您不能启用一个,而不能启用另一个。由于ZGC 今天不支持Compressed Oops,这意味着ZGC也没有充分的理由被禁止使用Compressed Class Pointer。在JDK 15中,压缩类指针和压缩Oops之间的人为依赖被打破,结果ZGC现在可以很好地使用压缩类指针。
 
类级别数据共享
HotSpot中的类数据共享(CDS)功能有助于减少JVM多个实例之间的启动时间和内存占用。仅在启用了“ Compressed Oops”功能(-XX:+UseCompressedOops)时,此功能才有效。在JDK 15中,增强了类数据共享,以在禁用“ Compressed Oops”功能时也可以使用。因此,类数据共享现在可以与ZGC(已经禁用了“ Compressed Oops”功能)一起很好地配合使用。