ZGC垃圾收集器

转载自:https://blog.csdn.net/huaishu/article/details/103596531
    https://houbb.github.io/2018/11/28/java-gc-optimize
    https://www.jianshu.com/p/4e4fd0dd5d25

  
  Java11已经推出最新垃圾收集器,ZGC主要为了减少JVM停顿时间。
  ZGC全称是Z Garbage Collector,是一款可伸缩(scalable)的低延迟(low latency garbage)、并发(concurrent)垃圾回收器,旨在实现以下几个目标:

  • 停顿时间不超过10ms
  • 停顿时间不随heap大小或存活对象大小增大而增大
  • 可以处理从几百兆到几T的内存大小(最大4T)

  

主要实现技术

指针标记(Pointer tagging Or Colored Pointers )
  ZGC利用指针的64位中的几位表示Finalizable、Remapped、Marked1、Marked0,以标记该指向内存的存储状态。相当于在对象的引用上标注了对象的信息(不是对象头)。在这个被指向的内存发生变化的时候(内存在Compact整理被移动时),颜色就会发生变化。

  • Marked0/marked1: 判断对象是否已标记
  • Remapped: 判断应用是否已指向新的地址
  • Finalizable: 判断对象是否只能被Finalizer访问

  这几个bits在不同的状态也就代表这个引用的不同颜色

  为什么有2个mark标记?每一个GC周期开始时,会交换使用的标记位,使上次GC周期中修正的已标记状态失效,所有引用都变成未标记。

  • GC周期1:使用mark0, 则周期结束所有引用mark标记都会成为01。

  • GC周期2:使用mark1, 则期待的mark标记10,所有引用都能被重新标记。

  
GC屏障 (GC Barriers)
  由于着色指针的存在,在程序运行时访问对象的时候,可以轻易知道对象在内存的存储状态(通过指针访问对象),若请求读的内存在被着色了,那么则会触发读屏障,读屏障会更新指针再返回结果,此过程有一定的耗费,从而达到与用户线程并发的效果。

  与标记对象的传统算法相比,ZGC在指针上做标记,在访问指针时加入Load Barrier(读屏障),比如当对象正被GC移动,指针上的颜色就会不对,这个屏障就会先把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速,而不存在为了保持应用与GC一致而粗暴整体的Stop The World。

  

原理

  逻辑上一次ZGC分为Mark(标记)、Relocate(迁移)、Remap(重映射)三个阶段

  • Mark: 所有活的对象都被记录在对应Page的Livemap(活对象表,bitmap实现)中,以及对象的Reference(引用)都改成已标记(Marked0或Marked1)状态
  • Relocate: 根据页面中活对象占用的大小选出的一组Page,将其中的活对象都复制到新的Page,并在额外的forward table(转移表)中记录对象原地址和新地址对应关系
  • Remap: 所有Relocated的活对象的引用都重新指向了新的正确的地址

  实现上,由于想要将所有引用都修正过来需要跟Mark阶段一样遍历整个对象图,所以这次的Remap会与下一次的Remark阶段合并。所以在GC的实现上是2个阶段,即Mark&Remap阶段和Relocate阶段

标记
  GC循环的第一部分是标记。标记包括查找和标记运行中的应用程序可以访问的所有堆对象,换句话说,查找不是垃圾的对象。
  
  ZGC的标记分为三个阶段。
  第一阶段是STW,其中GC roots被标记为活对象。 GC roots类似于局部变量,通过它可以访问堆上其他对象。 如果一个对象不能通过遍历从roots开始的对象图来访问,那么应用程序也就无法访问它,则该对象被认为是垃圾。从roots访问的对象集合称为Live集。GC roots标记步骤非常短,因为roots的总数通常比较小。
  该阶段完成后,应用程序恢复执行,ZGC开始下一阶段,该阶段同时遍历对象图并标记所有可访问的对象。 在此阶段期间,读屏障针使用掩码测试所有已加载的引用,该掩码确定它们是否已标记或尚未标记,如果尚未标记引用,则将其添加到队列以进行标记。
  在遍历完成之后,有一个最终的,时间很短的的Stop The World阶段,这个阶段处理一些边缘情况(我们现在将它忽略),该阶段完成之后标记阶段就完成了。

  
重定位
  GC循环的下一个主要部分是重定位。重定位涉及移动活动对象以释放部分堆内存。 为什么要移动对象而不是填补空隙? 有些GC实际是这样做的,但是它导致了一个不幸的后果,即分配内存变得更加昂贵,因为当需要分配内存时,内存分配器需要找到可以放置对象的空闲空间。 相比之下,如果可以释放大块内存,那么分配内存就很简单,只需要将指针递增新对象所需的内存大小即可。
  ZGC将堆分成许多页面,在此阶段开始时,它同时选择一组需要重定位活动对象的页面。选择重定位集后,会出现一个Stop The World暂停,其中ZGC重定位该集合中root对象,并将他们的引用映射到新位置。与之前的Stop The World步骤一样,此处涉及的暂停时间仅取决于root的数量以及重定位集的大小与对象的总活动集的比率,这通常相当小。所以不像很多收集器那样,暂停时间随堆增加而增加。

  移动root后,下一阶段是并发重定位。 在此阶段,GC线程遍历重定位集并重新定位其包含的页中所有对象。 如果应用程序线程试图在GC重新定位对象之前加载它们,那么应用程序线程也可以重定位该对象,这可以通过读屏障(在从堆加载引用时触发)实现,这可确保应用程序看到的所有引用都已更新,并且应用程序不可能同时对重定位的对象进行操作。

  GC线程最终将对重定位集中的所有对象重定位,然而可能仍有引用指向这些对象的旧位置。 GC可以遍历对象图并重新映射这些引用到新位置,但是这一步代价很高昂。 因此这一步与下一个标记阶段合并在一起。在下一个GC周期的标记阶段遍历对象对象图的时候,如果发现未重映射的引用,则将其重新映射,然后标记为活动状态。

你可能感兴趣的:(#,【GC相关】)