JVM-新一代GC之低延迟垃圾收集器

低延迟垃圾收集器

Shenandoah和ZGC为何被称为低延迟GC,由于它几乎整个工做过程所有都是并发的,只有初始标记、最终标记这些阶段有短暂的停顿,这部分停顿的时间基本上是固定的,与堆的容量、堆中对象的数量没有正比例关系。实际上,它们均可以在任意可管理的(譬如如今ZGC只能管理4TB之内的堆)堆容量下,实现垃圾收集的停顿都不超过十毫秒这种之前听起来是天方夜谭、匪夷所思的目标。这两款目前仍处于实验状态的收集器,被官方命名为“低延迟垃圾收集器”。java

衡量垃圾收集器的三项最重要的指标是:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency),三者共同构成了一个“不可能三角”。git

1. Shenandoah垃圾回收器

比起稍后要介绍的有着Oracle正朔血统的ZGC,Shenandoah反而更像是G1的下一代继承者。使用转发指针(Forwarding Pointer,也常被称为Indirection Pointer)来实现对象移动与用户程序并发的一种解决方案。github

那Shenandoah相比起G1又有什么改进呢?

虽然Shenandoah也是使用基于Region的堆内存布局,一样有着用于存放大对象的HumongousRegion,默认的回收策略也一样是优先处理回收价值最大的Region……但在管理堆内存方面,它与G1至少有三个明显的不一样之处,最重要的固然是支持并发的整理算法,G1的回收阶段是能够多线程并行的,但却不能与用户线程并发,这点做为Shenandoah最核心的功能稍后笔者会着重讲解。其次,Shenandoah(目前)是默认不使用分代收集的,换言之,不会有专门的新生代Region或者老年代Region的存在,没有实现分代,并非说分代对Shenandoah没有价值,这更可能是出于性价比的权衡,基于工做量上的考虑而将其放到优先级较低的位置上。最后,Shenandoah摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改用名为“链接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系,下降了处理跨代指针时的记忆集维护消耗,也下降了伪共享问题。算法

Shenandoah收集器的工做过程大体能够划分为如下九个阶段
  1. 初始标记 这个阶段还是“Stop The World”的,但停顿时间与堆大小无关,只与GC Roots的数量相关数据结构

  2. 并发标记 与G1同样,遍历对象图,标记出所有可达的对象,这个阶段是与用户线程一块儿并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。多线程

  3. 最终标记 与G1同样,处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(Collection Set)。最终标记阶段也会有一小段短暂的停顿。架构

  4. 并发清理 这个阶段用于清理那些整个区域内连一个存活对象都没有找到的Region并发

  5. 并发回收 在这个阶段,Shenandoah要把回收集里面的存活对象先复制一份到其余未被使用的Region之中。复制对象这件事情若是将用户线程冻结起来再作那是至关简单的,但若是二者必需要同时并发进行的话,就变得复杂起来了。其困难点是在移动对象的同时,用户线程仍然可能不停对被移动的对象进行读写访问,移动对象是一次性的行为,但移动以后整个内存中全部指向该对象的引用都仍是旧对象的地址,这是很难一瞬间所有改变过来的。对于并发回收阶段遇到的这些困难,Shenandoah将会经过读屏障和被称为“Brooks Pointers”的转发指针来解决(讲解完Shenandoah整个工做过程以后笔者还要再回头介绍它)。并发回收阶段运行的时间长短取决于回收集的大小布局

  6. 初始引用更新 并发回收阶段复制对象结束后,还须要把堆中全部指向旧对象的引用修正到复制后的新地址,这个操做称为引用更新。性能

  7. 并发引用更新 真正开始进行引用更新操做,这个阶段是与用户线程一块儿并发的,时间长短取决于内存中涉及的引用数量的多少。并发引用更新与并发标记不一样,它再也不须要沿着对象图来搜索,只须要按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改成新值便可。

  8. 最终引用更新 解决了堆中的引用更新后,还要修正存在于GC Roots中的引用。这个阶段是Shenandoah的最后一次停顿,停顿时间只与GC Roots的数量相关。

  9. 并发清理 通过并发回收和引用更新以后,整个回收集中全部的Region已再无存活对象,最后再调用一次并发清理过程来回收这些Region的内存空间,供之后新对象分配使用。

Shenandoah收集器性能

2016年作该测试时的Shenandoah并无彻底达成预约目标,停顿时间比其余几款收集器确实有了质的飞跃,但也并未实现最大停顿时间控制在十毫秒之内的目标,而吞吐量方面则出现了很明显的降低。

Shenandoah的性能在日益改善,逐步接近“Low-Pause”的目标。此外,RedHat也积极拓展Shenandoah的使用范围,将其Backport到JDK 11甚至是JDK 8之上,让更多不方便升级JDK版本的应用也可以享受到垃圾收集器技术发展的最前沿成果。

2. ZGC收集器

ZGC是一款在JDK 11中新加入的具备实验性质[插图]的低延迟垃圾收集器,是由Oracle公司研发的。2018年Oracle建立了JEP 333将ZGC提交给OpenJDK,推进其进入OpenJDK 11的发布清单之中。

ZGC和Shenandoah的目标是高度类似的,都但愿在尽量对吞吐量影响不太大的前提下,实如今任意堆内存大小下均可以把垃圾收集的停顿时间限制在十毫秒之内的低延迟。可是ZGC和Shenandoah的实现思路又是差别显著的。

ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。

并发整理算法的实现

Shenandoah使用转发指针和读屏障来实现并发整理,ZGC虽然一样用到了读屏障,但用的倒是一条与Shenandoah彻底不一样,更加复杂精巧的解题思路。

ZGC收集器有一个标志性的设计是它采用的染色指针技术(Colored Pointer),直接把标记信息记在引用对象的指针上。指针对于计算机来说,它也是一个信息的载体,可是目前而言,内存中的理论可访问信息是远大于实际需求的,尽管Linux高18位不能用来寻址,但剩余的46位也足以知足需求,因此ZGC团队就将指针信息载体进行染色,将其高4位用来存储四个记号信息,经过这些标志位,虚拟机能够直接从指针中看到其引用对象的三色标记状态、是否进入了重分配集(即被移动过)、是否只能经过finalize()方法才能被访问到。

因为这些标志位进一步压缩了本来就只有46位的地址空间,也直接致使ZGC可以管理的内存不能够超过4TB(2的42次幂)。

染色指针的三大优点:
  1. 染色指针可使得一旦某个Region的存活对象被移走以后,这个Region当即就可以被释放和重用掉,而没必要等待整个堆中全部指向该Region的引用都被修正后才能清理。

  2. 染色指针能够大幅减小在垃圾收集过程当中内存屏障的使用数量,设置内存屏障,尤为是写屏障的目的一般是为了记录对象引用的变更状况,若是将这些信息直接维护在指针中,显然就能够省去一些专门的记录操做。

  3. 染色指针能够做为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便往后进一步提升性能。

Java虚拟机做为一个普普统统的进程,这样随意从新定义内存中某些指针的其中几位,操做系统是否支持?处理器是否支持?

这里面的解决方案要涉及虚拟内存映射技术。把染色指针中的标志位看做是地址的分段符,那只要将这些不一样的地址段都映射到同一个物理内存空间,通过多重映射转换后,就可使用染色指针正常进行寻址了。

image

ZGC的运做过程大体可划分为如下四个大的阶段
  1. 并发标记(Concurrent Mark):并发标记是遍历对象图作可达性分析的阶段,与G一、Shenandoah不一样的是,ZGC的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked 0、Marked 1标志位。

  2. 并发预备重分配(Concurrent Prepare for Relocate):这个阶段须要根据特定的查询条件统计得出本次收集过程要清理哪些Region,ZGC划分Region的目的并不是为了像G1那样作收益优先的增量回收,而实用范围更大的扫描成本换取省去G1中记忆集的维护成本。此外,在JDK12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段中完成的。

  3. 并发重分配(Concurrent Relocate):重分配是ZGC执行过程当中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每一个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,若是用户线程此时并发访问了位于重分配集中的对象,此次访问将会被预置的内存屏障所截获,而后当即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。

  4. 并发重映射(Concurrent Remap):重映射所作的就是修正整个堆中指向重分配集中旧对象的全部引用。ZGC的并发重映射并非一个必需要“迫切”去完成的任务,由于前面提到ZGC有"自愈"能力,最坏也就多跳转一层,这时候,一旦全部指针都被修正以后,原来记录新旧对象关系的转发表就能够释放掉了。

ZGC收集器性能
  1. ZGC的设计理念是迄今垃圾收集器研究的最前沿成果,但是,一定要有优有劣才会称做权衡,ZGC的这种选择[插图]也限制了它能承受的对象分配速率不会过高,目前惟一的办法就是尽量地增长堆容量大小,得到更多喘息的时间。

  2. ZGC还有一个常在技术资料上被说起的优势是支持“NUMA-Aware”非统一内存访问架构的内存分配。因为摩尔定律逐渐失效,现代处理器因频率发展受限转而向多核方向发展,这就形成了若是要访问被其余处理器核心管理的内存,就必须经过Inter-Connect通道来完成,这要比访问处理器的本地内存慢得多,ZGC收集器会优先尝试在请求线程当前所处的处理器的本地内存上分配对象,以保证高效内存访问。

  3. 在ZGC的“弱项”吞吐量方面,以低延迟为首要目标的ZGC已经达到了以高吞吐量为目标Parallel Scavenge的99%,直接超越了G1。

  4. ZGC均能绝不费劲地控制在十毫秒以内

image

image

声明:本问参考书籍《深刻理解Java虚拟机:JVM高级特性与最佳实践(第3版) 》,这个版本刚上市两个月,新增了一些新的GC和内存分配策略的知识,大伙有兴趣能够看看。如有侵权,请联系删除,谢谢!



若是你喜欢个人文章,那麻烦请关注个人公众号,该公众号还处于初始阶段,谢谢你们的支持。

关注公众号,回复 java架构获取架构视频资源(后期还会分享不一样的优质资源噢)。回复 找对象能够拉你进IT单身交友群噢。

想看往期文章, 请点击个人GitHub地址: github.com/fantj2016/j…

相关文章
相关标签/搜索