一、判定对象存活的算法
引用计数法
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一,当引用失效时,计数器值就减一,任何时刻计数器为零的对象就是不能再被使用的对象。 原理简单,判定效率高,但是存在对象之间相互循环引用的问题。
可达性分析算法
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,则证明这个对象是不可能再被使用的对象。
二、垃圾收集算法
标记-清除算法
标记-清除算法是最早出现也是最基础的垃圾收集算法,分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
缺点
- 执行效率不稳定,标记和清除两个过程的执行效率都会随着对象数量增长而降低。
- 存在内存空间碎片化问题,标记、清除之后会产生大量不连续的内存碎片,可能会导致分配大对象时无法找到足够的连续内存而不得不提前触发另一次GC。
标记-复制算法
标记-复制算法将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次性清理掉。考虑到一般情况下大多数对象都是可回收的,所以需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,不需要考虑内存碎片的问题。
缺点
- 可用内存缩小为原来的一半,浪费内存空间。(考虑到大多数新生代对象都是可回收的,后续就有了Eden区和Survivor区8:1的比例)
- 在对象存活率较高时要进行较多的复制操作,执行效率会降低。
标记-整理算法
标记-整理算法是在标记-清除的基础上进行了一次整理,即让所有存活的对象都想内存空间一端移动,然后直接清理掉边界意外的内存,避免产生内存碎片的问题。
缺点
如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行。
三、垃圾收集器
Serial收集器
Serial收集器,也叫串行GC,是最基础、历史最悠久的收集器。这个收集器是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
ParNew收集器
ParNew收集器,可以理解为是Serial收集器的多线程并行版本。一般与CMS收集器配合工作,是激活CMS后默认的新生代收集器,新生代用ParNew,老年代用CMS。
Parallel Scavenge收集器
Parallel Scavenge收集器,是一款新生代收集器,基于标记-复制算法实现,也是能够并行收集的多线程收集器,它的目标是达到一个可控制的吞吐量,被称为“吞吐量优先收集器”。提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。有两种用途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种就是作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合。
CMS收集器
CMS(Concurrent Mark Sweep)收集器,是一种以获取最短回收停顿时间为目标的收集器,关注服务的响应速度,基于标记-清除算法实现。
运行步骤
1)初始标记(CMS initial mark) 2)并发标记(CMS concurrent mark) 3)重新标记(CMS remark) 4)并发清除(CMS concurrent sweep) 缺点:
- 在处理器核心数较低时,并发回收时用户线程的执行速度会降低,影响吞吐量;
- 并发收集的过程中用户线程会不断地产生新的垃圾(浮动垃圾),这些垃圾无法处理;
- 由于采用标记-清除算法,所以收集结束时会产生大量空间碎片,在分配大对象时可能会因为无法找到足够大的连续空间而出发一次Full GC。
Garbage First收集器
Garbage First收集器,简称G1 GC,是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式,是JDK9之后默认的GC。
特点
- 基于Region的堆内存布局,仍然遵循分代收集理论:默认2048个Region,每个1MB~32MB之间,且为2的N次幂,超过容量一半的视为大对象,存在Humongouse对象中
- 可以根据用户设定的收集停顿时间(默认200ms)进行垃圾收集
- 优先回收价值(即回收所获得的空间大小以及回收所需时间的经验值)利益最大的Region,后台维护一个优先级列表
- 处理跨Region引用关系使用记忆集
运行步骤
1)初始标记(Initial Marking) 2)并发标记(Concurrent Marking) 3)最终标记(Final Marking) 4)筛选回收(Live Data Counting and Evacuation)
如何解决跨Region引用
使用记忆集避免全堆作为GC Roots扫描,每个Region都维护有自己的记忆集,这些记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。G1 GC的记忆集用卡表来实现,本质上是一种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。
Shenandoah收集器
Shenandoah收集器,OpenJDK 12的特性之一,它的目标是能在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的垃圾收集器。
特点
- 基于Region的堆内存布局
- 支持并发的标记-整理算法
- 默认不使用分代收集
- 使用读屏障和转发指针
- 处理跨Region引用关系使用连接矩阵
运行步骤
1)初始标记 2)并发标记 3)最终标记 4)并发清理 5)并发回收 6)初始引用更新 7)并发引用更新 8)最终引用更新 9)并发清理
ZGC收集器
ZGC收集器,是一款以低延迟为目标垃圾收集器,希望能实现在任务堆内存大小下都可以把垃圾收集的停顿时间限制在10毫秒以内。
特点
- 基于Region的堆内存布局:小型Region(容量2MB,小于256KB的小对象),中型Region(容量32MB,大于等于256KB但小于4MB的对象),大型Region(容量不固定,必须为2MB的整数倍,大于等于4MB的大对象)
- 支持并发的标记-整理算法
- 默认不使用分代收集
- 使用读屏障、染色指针、内存多重映射等技术
运行步骤
1)并发标记 2)并发预备重分配 3)并发重分配 4)并发重映射
四、常见的GC组合
新生代 | 老年代 | 适用场景 |
---|
Serial | Serial Old | 实现单线程的低延迟垃圾回收机制 | ParNew | CMS | 实现多线程的低延迟垃圾回收机制 | Parallel Scavenge | Parallel Scavenge Old | 实现多线程的高吞吐量垃圾回收机制 |
五、各个GC的对比
收集器 | 串行/并行/并发 | 新生代/老年代 | 算法 | 目标 | 适用场景 |
---|
Serial | 串行 | 新生代 | 标记-复制 | 响应速度优先 | 单CPU环境下的Client模式 | Serial Old | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单CPU环境下的Client模式,CMS的后备预案 | ParNew | 并行 | 新生代 | 标记-复制 | 响应速度优先 | 多CPU环境下时在Server模式下与CMS配合 | Parallel Scavenge | 并行 | 新生代 | 标记-复制 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | Parallel Old | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | CMS | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站活B/S系统服务端的Java应用 | G1 | 并发 | 新生代和老年代 | 标记-整理、复制 | 响应速度优先 | 面向服务端应用,将来替换CMS | Shenandoah | 并发 | 不分代 | 并发整理 | 垃圾收集停顿时间限制在10ms以内 | 面向服务端应用 | ZGC | 并发 | 不分代 | 并发整理 | 垃圾收集停顿时间限制在10ms以内 | 面向服务端应用 |
六、基于Region堆内存布局的GC对比
收集器 | 是否分代 | Region类型 | 如何解决跨代/跨Region引用 | 特点 |
---|
G1 | 是 | Region、Humongous Region | 记忆集(卡表) | 写屏障、原始快照 | Shenandoah | 否 | Region、Humongous Region | 连接矩阵 | 读屏障、转发指针 | ZGC | 否 | 小型Region、中型Region、大型Region | 染色指针 | 读屏障、染色指针、内存多重映射 |
七、各个JDK版本默认的GC
JDK版本 | 默认GC |
---|
6、7、8 | Parallel | 9以及9以上 | G1 |
八、GC总结
收集器 | 特点 |
---|
串行GC(Serial GC) | 单线程执行,应用需要暂停 | 并行GC(ParNew、Parallel Scavenge、Parallel Old) | 多线程并行执行垃圾回收,关注于吞吐量 | CMS(Concurrent Mark-Sweep) | 多线程并发标记和清除,关注于降低延迟 | G1(Garbage First) | 通过划分多个内存区域做增量整理和回收,进一步降低延迟 | ZGC(Z Garbage Collector) | 通过染色指针和读屏障,实现几乎全部的并发执行,10ms以内的延迟 | Shenandoah | G1的改进版本,与ZGC类似 |
|