总览
处于不同区域的垃圾收集器负责该区域(年轻代、老年代)的收集工作。有连线的表示可以搭配使用
Serial收集器
Serial收集器是一个单线程的垃圾收集器,这里的单线程强调的是进行立即收集时,其他线程必须暂停(stop the world)。运行如下:但该收集器是所有收集中额外耗费内存(memory Footprint 指的是保证垃圾收集器能够顺利高效的进行而存储的额外信息)最小的。
ParNew收集器
ParNew其实是Serial的多线程并行版本,除了Serial之外,目前只有它能配合CMS工作,其工作流程如图所示:
它默认开启的收集线程数与处理器核心数量相同。在这种多核处理器的条件下,效率自然高于serial收集器。这里解释一下垃圾收集中常常混淆的并发和并行两个概念。
- 并发:并发描述的是垃圾收集线程和用户线程之间的关系,表示同一时间有多条垃圾收集线程在工作
- 并行:并行描述的是多个垃圾收集之间的关系,说明同一时间,垃圾线程和用户线程都在运行
Parallel Scavenge收集器
Parallel Scavenge收集器跟ParNew很相似,不同点在于该收集器更关注可控制的吞吐量,吞吐量指的是:
Parallel Scavenge通过以下两个参数精确控制吞吐量:
- -XX: MaxGCPauseMillis 最大垃圾收集停顿时间
- -XX: GCTimeRatio 吞吐量大小
-XX: MaxGCPauseMillis是一个大于0的毫秒数,当然不能指望设置的越小越好,收集器只会尽可能的完成这个目标,同时花费时间减少是通过牺牲年轻代内存大小和吞吐量完成的。导致垃圾收集更频繁,吞吐量下降。
除此之外,Parallel Scavenge收集器还有一个参数值得关注: - -XX:+UseAdaptiveSizePolicy
该参数被激活后,表示不需要人工指定新生代的大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivoRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数 虚拟机会动态调整以提供最合适的停顿时间和吞吐量。
Serial Old收集器
Serial Old收集器是Serial的老年代版本,同样是单线程收集器,使用标记-整理算法,用途如下:
- 在客户端模式下,供HotSpot虚拟机使用
-
服务端模式下,在Jdk5之前与Parallel Scavenge搭配使用,另一种是作为CMS收集器的失败后备预案。
Parallel Old收集器
Parallel Old是Parallel Scavenge的老年代版本,多线程,标记整理算法,两者搭配使用,成为名副其实的“吞吐量优先”的搭配组合。在吞吐量和资源稀缺的场合,可以优先考虑该搭配组合。
CMS收集器
CMS收集器是一种旨在获取最短停顿时间的垃圾收集器,运作过程包含以下四个步骤:
- 初始标记
- 并发标记
- 重新标记
-
并发清除
其中初始标记和重新标记是需要暂停用户线程的,并发标记和并发清除是和用户线程一起执行的,执行流程图如下所示:
虽然CMS收集器是一个低停顿、并发收集的收集器,但还是存在以下缺点:
- CMS默认启动的回收线程数是(处理器核心数量+3) /4, 也就是说, 如果处理器核心数在四个或以上, 并发回收时垃圾收集线程只占用不超过25%的处理器运算资源, 并且会随着处理器核心数量的增加而下降。 但是当处理器核心数量不足四个时,CMS对用户程序的影响就可能变得很大
- 浮动垃圾
浮动垃圾是指在CMS收集的并发标记和并发清理过程中、由于是和用户线程一起运行的,用户线程就会不断产生新的垃圾对象,而这些对象又是在垃圾收集后出现的。CMS收集器只能在下次垃圾收集时才能进行回收,这一部分被称为“浮动垃圾”。同时不同于其他收集器可以等到老年代满了才进行垃圾回收,CMS是并发的,因此要预留空间给程序运行使用。所以通过-XX: CMSInitiatingOccupancyFraction参数设置触发CMS收集的老年代空间占用比例。但是这个参数调的过高可能会引起Concurrent Mode Failure,表示CMS预留的空间不足于程序新分配的对象,此时CMS会启动后备方案,通过Serial OLd收集器进行老年代回收 - 空间碎片化 因为CMS收集器是采用的标记-清除算法
Garbage First收集器
开创了面向局部收集的设计思路和基于Region的内存布局形式,是一款面向服务端的收集器,从JDK9开始,替代了Parallel Scavenge搭配Parallel Old的收集器,成为服务端默认的垃圾收集器。
G1不再坚持固定大小以及固定数量的分代区域划分, 而是把连续的Java堆划分为多个大小相等的独立区域(Region) , 每一个Region都可以根据需要, 扮演新生代的Eden空间、 Survivor空间, 或者老年代空间。
G1收集器中虽然还是存在新生代、老年代,但是不再是固定的,而是不需要连续的动态集合,它将Region作为最小的回收单元,每个Region可以通过-XX:G1HeapRegionSize设置大小,Region中包含一个Humongous区域,存储大对象,对于超过Region一半大小的对象就存放在N个连续的Region的Humongous区域。
G1的收集思路是跟踪每个Region的垃圾收集价值大小,即回收所获得的空间大小和回收所需时间的经验值,维护一个优先级列表,回收时优先回收价值大的。
G1缺点:
- 需要为每一个Region维护一个卡表 内存占用较高
- 执行负载高
Shenandoah收集器
Shenandoah收集器对比G1有所改进,例如:
- 支持并发回收 ,G1虽然可以多线程执行回收但是不能和用户线程并发执行
- Shenandoah不支持分代收集
- 摒弃记忆集 使用连接矩阵记录跨Region的引用关系
该收集器大致分为以下9个阶段:
1.初始标记 标记与GC Roots直接关联的对象 需暂停用户线程
2.并发标记 与G1一样, 遍历对象图, 标记出全部可达的对象
3.最终标记 处理剩余的SATB扫描, 并在这个阶段统计出回收价值
最高的Region, 将这些Region构成一组回收集 需暂停用户线程
4.并发清理 清理那些整个区域内连一个存活对象都没有找到
的Region
5.并发回收 Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中,通过转发指针“Brooks Pointers”实现对象移动后 原本指向这些对象的引用的改变
6.初始引用更新 并发回收阶段复制对象结束后, 还需要把堆中所有指向旧对象的引用修正到复制后的新地址 暂停用户线程 引用更新的初始化阶段实际上并未做什么具体的处理, 设立这个阶段只是为了建立一个线程集合点, 确保所有并发回收阶段中进行的收
集器线程都已完成分配给它们的对象移动任务而已
7.并发引用更新 真正开始进行引用更新操作
8.最终引用更新 解决了堆中的引用更新后, 还要修正存在于GC Roots中的引用 暂停用户线程
9.并发清理
转发指针 Brooks pointer
原有对象布局结构的最前面统一增加一个新的引用字段, 在正常不处于并发移动的情况下, 该引用指向对象自己
ZGC收集器
ZGC收集器是一款基于Region内存布局的, (暂时)不设分代的, 使用了读屏障、 染色指针和内存多重映射等技术来实现可并发的标记-整理算法的, 以低延迟为首要目标的一款垃圾收集器。
ZGC虽然也是基于Region收集,但其将Region划分为了大、中、小3类容量
大型Region存放的是4Mb及以上大小的对象,容量不固定,每个Region只存放一个大型对象,同时该Region中的对象不会重分配
染色指针
ZGC收集器采用染色指针使得一旦某个Region的存活对象被移走之后, 这个Region立即就能够被释放和重用掉, 而不必等待整个堆中所有指向该Region的引用都被修正后才能清理。
ZGC分为以下四个阶段:
1.并发标记
与G1、 Shenandoah不同的是, ZGC的标记是在指针上而不是在对象上进行的
2.并发预备重分配
根据特定的查询条件统计得出本次收集过程要清理哪些Region, 将这些Region组成重分配集(Relocation Set)
3.并发重分配
重分配是ZGC执行过程中的核心阶段, 这个过程要把重分配集中的存活对象复制到新的Region上, 并为重分配集中的每个Region维护一个转发表(ForwardTable) , 记录从旧对象到新对象的转向关系
4.并发重映射
重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用