3.5 经典垃圾收集器

3.5.1 Serial收集器

单线程,HotSpot虚拟机运行在客户端模式下的默认新生代收集器,对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最小的,微服务

3.5.2 ParNew收集器

Serial收集器的多线程并行版本,除了Serial收集器外,目前只有它能与CMS收集器配合工作

3.5.3 Parallel Scavenge收集器

吞吐量优先收集器

吞吐量(Throughput):处理器用于运行用户代码的时间/处理器总消耗时间 参数-XX:

  • MaxGCPauseMillis:最大垃圾收集停顿时间,ms
  • GCTimeRatio:吞吐量大小,{1/(1+x),默认x=99}
  • +UseAdaptiveSizePolicy:自适应的调节策略(GC Ergonomics)

3.5.4 Serial Old收集器

Serial收集器的老年代版本

3.5.5 Parallel Old收集器

Parallel Scavenge收集器的老年代版本

3.5.6 CMS收集器(Concurrent Mark Sweep)

以获取最短回收停顿时间为目标的收集器,B/S系统

3.5.7 Garbage First收集器(G1)

使用Region划分内存空间,以及具有优先级的区域回收方式

停顿时间模型(Pause Prediction Model):在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒

Mixed GC:面向堆内存任何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大

Region堆内存布局:把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理.Humongous区域:专门用来存储大对象

3.6 低延迟垃圾收集器

3.6.1 Shenandoah收集器

并发标记(Concurrent Marking):与G1一样,遍历对象图,标记出全部可达的对象,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。

并发回收(Concurrent Evacuation):并发回收阶段是Shenandoah与之前HotSpot中其他收集器的核心差异。在这个阶段,Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中。通过读屏障和被称为“Brooks Pointers”的转发指针来解决。并发回收阶段运行的时间长短取决于回收集的大小。

并发引用更新(Concurrent Update Reference):真正开始进行引用更新操作,时间长短取决于内存中涉及的引用数量的多少。按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为新值即可。

Brooks Pointers:在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己

  • 每次对象访问会带来一次额外的转向开销
  • 并发写入:通过比较并交换(Compare And Swap,CAS)操作来保证并发时对象的访问正确性
  • 执行频率的问题:通过对象头上的Brooks Pointer来保证并发时原对象与复制对象的访问一致性,要覆盖全部对象访问操作,Shenandoah不得不同时设置读、写屏障去拦截。
    • 引用访问屏障(Load Reference Barrier):只拦截对象中数据类型为引用类型的读写操作,而不去管原生数据类型等其他非引用字段的读写,这能够省去大量对原生类型、对象比较、对象加锁等场景中设置内存屏障所带来的消耗

3.6.2 ZGC收集器(Z Garbage Collector)

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

Region:具有动态性——动态创建和销毁,以及动态的区域容量大小

  • 小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象
  • 中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象
  • 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,这也预示着虽然名字叫作“大型Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的实现中是不会被重分配的,因为复制一个大对象的代价非常高昂

染色指针技术(Colored Pointer):把标记信息记在引用对象的指针上,这时,与其说可达性分析是遍历对象图来标记对象,还不如说是遍历“引用图”来标记“引用”了

ZGC运作过程

  • 并发标记(Concurrent Mark):标记阶段会更新染色指针中的Marked 0、Marked 1标志位
  • 并发预备重分配(Concurrent Prepare for Relocate):根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)
  • 并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系
  • 指针的自愈(Self-Healing):得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象
    • 只有第一次访问旧对象会陷入转发,也就是只慢一次,对比Shenandoah的Brooks转发指针,那是每次对象访问都必须付出的固定开销,简单地说就是每次都慢
    • 由于染色指针的存在,一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配(但是转发表还得留着不能释放掉),哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的
  • 并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,这一点从目标角度看是与Shenandoah并发引用更新阶段一样的,但是ZGC的并发重映射并不是一个必须要“迫切”去完成的任务,因为前面说过,即使是旧引用,它也是可以自愈的,最多只是第一次使用时多一次转发和修正操作。重映射清理这些旧引用的主要目的是为了不变慢(还有清理结束后可以释放转发表这样的附带收益),所以说这并不是很“迫切”。因此,ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象图的开销。一旦所有指针都被修正之后,原来记录新旧对象关系的转发表就可以释放掉了