垃圾收集器与GC日志
ZGC收集器(Z Garbage Collector,全并发、超低延迟 10ms)
ZGC是一款JDK11中新加入的具有实验性质的低延迟垃圾收集器,ZGC可以说源自于Azul System公司开发的C4(Concurrent Continuously Compacting Collector)收集器
参考文章:https://wiki.openjdk.java.net/display/zgc/Main
http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf
目标
- 1.支持TB量级的堆。一般生产环境的硬盘还没有上TB呢,这应该可以满足未来十年内,所有Java应用的需求了吧
- 2.最大GC停顿时间不超过10ms.目前一般线上环境运行良好的Java应用Minor GC停顿时间在10ms左右,Major GC一般都需要100ms以上(G1可以调节停顿时间,但是如果调的过低的话,反而会适得其反),之所以能做到这一点是因为它的停顿时间主要跟Root扫描有关,而Root数量和堆大小是没有关系的
- 3.奠定未来GC特性的基础
- 4.最糟糕的情况下吞吐量会降低15%。这都不是事,停顿时间足够优秀。至于吞吐量,通过扩容分分钟解决。另外,Oracle官方提到了它的最大优点是:它的停顿时间不会随着堆的增大而增长!也就是说,几十G堆的停顿时间是10ms一下,几百G甚至上T堆的停顿时间也是10ms一下
不分代(暂时)
单代,即ZGC[没有分代]。我们知道以前的垃圾回收器之所以分代,是因为源于"[大部分对象朝生夕死]"的假设,事实上大部分系统的对象分配行为也确实符合这个假设,那么为什么ZGC就不分代呢?因为分代实现起来麻烦,作者就先实现出一个比较简单的单代版本。后续会优化
ZGC的内存布局
ZGC收集器是一款基于Region内存布局的,暂时不设分代的。使用了读屏障、颜色指针等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
ZGC的region可以具有如图所示的大、中、小三类容量:
- 1.小型Region(Small Region):容量固定为2MB,用户放置小于256KB的小对象(x < 256KB, x为对象大小)
- 2.中型Region(Medium Region):容量固定为32MB,用户放置大于等于256KB但小于4MB(256KB <= x < 4MB)
- 3.大型Region(Large region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象,每个大型Region中只会存放一个大对象,这也预示着虽然名字叫做"大型Region",但它的实际容量完全有可能小于中型Region,最小容量可低至4MB.大型Region在ZGC的实现中是不会被重分配的(重分配是ZGC的一种处理动作,用于复制对象的收集阶段),因为复制一个大对象的代价非常高昂。
NUMA-aware
NUMA对应的有UMA,UMA即Uniform Memory Access Architecture,NUMA就是Non Uniform Memory Access Architecture.UMA标识内存只有一块,所有的CPU都去访问这一块内存,那么就会存在竞争问题(争夺内存总线访问权),有金正就会有锁,有锁效率就会收到影响,而且CPU核心越多,竞争就越激烈。NUMA的话每个CPU对应有一块内存,且这块内存在主板上离这个CPU是最近的,每个CPU优先访问这块内存,那效率自然就提高了:
服务器的NUMA架构在中大型系统上一直非常盛行,也是高性能的解决方案,尤其在系统延迟方面表现都很优秀,ZGC是能自动感知NUMA架构并充分利用NUMA架构特性的
颜色指针
Colored Pointers,即颜色指针,如下图所示,ZGC的核心设计之一。以前的垃圾回收器的GC信息都保存在对象偷中,而ZGC的GC信息保存在指针中。
每个对象有一个64位指针,这64位被分为:
18Bit:预留给以后使用
1Bit:Finalizable标识,此位与并发引用处理有关,它表示这个对象只能通过finalizer才能访问
1Bit:Remapped标识,设置此位的值后,对象未指向relocation set中(relocation set标识需要GC的Region集合)
1Bit:Marked1标识
1B:Marked0标识,和上面的Marked1都是标记对象用于辅助GC
42位:对象的地址(所以它可以支持2^42 = 4T内存)
为什么有2个mark标记?
每一个GC周期开始时,会交换使用过的标记位,使上次GC周期中修正的已标记状态失效,所有引用都变成未标记。
GC周期1:使用mark 0,则周期结束后所有引用mark标记都会成为01
GC周期2:使用mark2,则期待的mark标记10,所有引用都能被重新标记
通过设置ZGC后对象指针分析我们可知,对象指针必须是64位,那么ZGC就无法支持32位操作系统,同样的也就无法支持压缩指针了(CompressedOops,压缩指针也是32位)。
颜色指针的三大优势:
- 1.一旦某个Region的存活对象被一走之后,这个Region立即就能够被释放和重用掉,而不必等待堆中所有指向该Region的引用都被修正后才能清理。这使得理论上只要还有一个空闲Region,ZGC就能完成收集
- 2.颜色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,ZGC只使用了读屏障
- 3.颜色指针具备强大的扩展性,它可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能
读屏障
之前的GC都是采用Write Barrier,这次ZGC采用了完全不同的方案读屏障,这个是ZGC一个非常重要的特性。在标记和移动对象的阶段,每次【从堆里对象的引用类型中读取一个指针】的时候,都需要加上一个Load Barrier.那么该如何理解它呢?看下面的代码,第一行代码我们尝试读取堆中的一个对象引用obj.fieldA并赋给引用o(fieldA也是一个对象时才会加上读屏障)。如果这时候对象在GC时被移动了,接下来JVM就会加上一个读屏障,这个屏障会把读出的指针更新到对象的新地址上,并且把堆里的这个指针"修正&#