ZGC在JDK15与JDK16的重要新特性

最近在整理GC相关文章,看到了关于ZGC在jdk15与16的新特性

https://malloc.se/blog/zgc-jdk15
https://malloc.se/blog/zgc-jdk16

在JDK15中

支持压缩类指针(非压缩指针)

Compressed class pointers

在原来的JDK中,由于ZGC使用了染色指针,所以不支持压缩指针,但是在JDK15中,支持了压缩指针。

压缩类指针
在HotSpot中,所有的Java对象都有一个由两个字段组成的头,一个标记字,一个类指针。在64位CPU上,这两个字段通常都是64位宽,其中类指针是一个指向内存的普通指针,描述对象的类(类型信息、vtable等)。压缩类指针功能(-XX:+UseCompressedClassPointers)通过减少所有对象头的大小来帮助减少整体堆的使用。它通过将类指针字段压缩到32位(而不是64位)来实现。压缩后的类指针不是一个普通的指针,而是一个进入压缩类空间的偏移,它有一个已知的基地址。要检索真正的类指针,JVM只需将(可能是位偏移的)压缩类指针添加到压缩类空间的基地址中。

压缩类指针功能的实现在历史上一直与压缩 Oops 功能紧密相连,这意味着如果不同时启用压缩 Oops,就无法启用压缩类指针。这只是一种人为的依赖关系,因为没有任何技术原因让你不能启用一个而不启用另一个。由于今天ZGC不支持压缩Oops,这意味着ZGC也被无端地阻止了压缩类指针的使用。
在 JDK 15 中,压缩类指针和压缩 Oops 之间的人为依赖性被打破了,因此 ZGC 现在可以很好地与压缩类指针配合使用。

https://malloc.se/blog/zgc-jdk15


在JDK16中

停顿时间将从小于10ms变为小于1ms

Sub-milliseond Max Pause Times

ZGC的停顿时间,在JDK16中,将会从 原来的小于10ms变为小于1ms,这代表着ZGC以后的停顿时间将会是一个定值,将与JVM中任何结构都无关。

当我们开始ZGC项目时,我们的目标是永远不要让GC暂停时间超过10ms。在当时,10ms似乎是一个雄心勃勃的目标。HotSpot中的其他GC通常提供的最大暂停时间要比这差好几个数量级,尤其是在使用大型堆时。达到这个目标在很大程度上是在一个并发阶段而不是在停止世界阶段完成所有真正繁重的工作,比如重新定位、引用处理和类卸载。当时,HotSpot缺乏很多并发完成这些工作所需的基础架构,所以花了几年的开发时间才达到这个目标。

在达到最初的10ms目标后,我们重新调整了目标,并将目标定在了更远大的地方。即GC暂停时间永远不能超过1ms。从JDK 16开始,我很高兴地报告,我们也达到了这个目标。ZGC现在的暂停时间为O(1)换句话说,它们是在恒定的时间内执行的,并且不会随着堆、活集或根集大小(或其他任何东西)的增加而增加。当然,我们还是要听从操作系统调度器的安排,给GC线程CPU时间。但只要你的系统没有严重超额供给,你可以期望看到GC平均暂停时间约为0.05ms(50µs),最大暂停时间约为0.5ms(500µs)。

那么,我们是怎么做到这一点的呢?在JDK 16之前,ZGC的暂停时间仍然随根集的大小(子集)而缩放。更准确地说,我们仍然在停止世界阶段扫描线程堆栈。这意味着,如果一个Java应用有大量的线程,那么暂停时间会增加。如果这些线程有很深的调用堆栈,那么暂停时间会增加得更多。从JDK 16开始,线程堆栈的扫描是并发进行的,即在Java应用程序继续运行的同时进行。

正如你可以想象的那样,在线程运行的同时,在线程堆栈中摸索,需要一点魔法。这是由一种叫做堆栈水印屏障的东西来实现的。简而言之,这是一种机制,它可以防止Java线程在没有首先检查是否安全的情况下返回到堆栈框架中。这是一种廉价的检查,它被折叠到已经存在的方法返回时的安全点检查中。从概念上讲,你可以把它看作是堆栈框架的负载屏障,如果需要的话,它将迫使Java线程在返回到堆栈框架之前采取某种形式的动作,使堆栈框架进入安全状态。每个Java线程都有一个或多个堆栈水印,水印告诉屏障,在没有任何特殊动作的情况下,走过堆栈多远是安全的。要走过一个水印,就要走一条慢路,使一个或多个帧进入当前安全状态,并更新水印。将所有线程栈带入安全状态的工作通常由一个或多个GC线程处理,但由于这是并发完成的,如果Java线程返回到GC尚未到达的帧中,有时就必须修复自己的几个帧。如果你对更多的细节感兴趣,请看一下JEP 376。ZGC: Concurrent Thread-Stack Processing, 其中描述了这项工作.

有了JEP 376,ZGC现在在Stop-The-World阶段扫描的根数正好为零。对于许多工作负载,即使在JDK 16之前,你也能看到非常低的最大暂停时间。但如果你在大型机器上运行,并且你的工作负载有大量的线程,你仍然可以看到远高于1ms的最大暂停时间。为了直观地看到改进,这里有一个比较JDK 15和JDK 16的例子,在一台有几千个Java线程的大型机器上运行SPECjbb®2015。

https://malloc.se/blog/zgc-jdk16


总结

ZGC打破了以前两个很重要的特征:

  1. 不支持压缩类指针压缩指针 -> JDK15以后开始支持压缩类指针
  2. parse时间在10ms以内 -> JDK16以后,ZGC的停顿时间为O(1),不会超过1ms。
标签:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

Captcha Code