一、基础垃圾回收算法
- 标记-清除(Mark-Sweep)
- 原理:
- 标记阶段:从 GC Roots(如静态变量、活动线程栈中的对象等)出发,遍历所有可达对象,标记为存活。
- 清除阶段:遍历堆内存,回收未被标记的对象。
- 优点:实现简单。
- 缺点:内存碎片化;两次遍历(标记和清除)导致效率较低。
- 应用场景:老年代(如 CMS 收集器)。
- 原理:
- 标记-整理(Mark-Sweep-Compact)
- 原理:
- 标记阶段:与标记-清除相同,标记存活对象。
- 整理阶段:将存活对象向内存一端移动,清理边界外的内存。
- 优点:无碎片化;内存利用率高。
- 缺点:整理阶段需移动对象,效率较低。
- 应用场景:老年代(如 Parallel Old、G1 收集器)。
- 原理:
- 复制(Copying)
- 原理:
- 将内存分为两块(如 Eden 区和 Survivor 区),每次只使用一块。
- 存活对象被复制到另一块,原内存块整体回收。
- 优点:无碎片化;适合存活率低的对象。
- 缺点:内存利用率低(需预留一半空间)。
- 应用场景:新生代(如 Serial、ParNew 等收集器)。
- 原理:
- 分代收集(Generational Collection)
- 核心思想:根据对象存活周期将堆划分为新生代(Young Generation)和老年代(Old Generation)。
- 新生代:对象存活率低,使用复制算法(如 Eden 区到 Survivor 区)。
- 老年代:对象存活率高,使用标记-清除或标记-整理算法。
- 优化逻辑:结合不同算法,平衡效率和内存利用率。
- 核心思想:根据对象存活周期将堆划分为新生代(Young Generation)和老年代(Old Generation)。
注: jvm未采用“引用计数法”算法,因为无法解决循环引用问题
二、Java 垃圾收集器
Java 虚拟机(JVM)基于上述算法实现了多种垃圾收集器,适用于不同场景:
- Serial 收集器
- 特点:单线程,简单高效;全程 STW(Stop-The-World)。
- 适用场景:客户端应用或小内存环境。
- Parallel Scavenge 收集器
- 特点:多线程,关注高吞吐量(Throughput)。
- 适用场景:后台计算型应用(如批处理)。
- ParNew 收集器
- 特点:Serial 的多线程版本,用于新生代。
- 适用场景:配合 CMS 收集器使用。
- CMS(Concurrent Mark-Sweep)收集器
- 特点:并发标记清除,减少 STW 时间;但内存碎片化严重。
- 阶段:
- 初始标记(STW): 标记GC Roots直接关联的对象(STW时间极短)
- 并发标记: 从GC Roots直接关联对象开始遍历整个引用链的过程(耗时长,不需要停顿用户线程,用户线程与GC线程并发执行)
- 重新标记(STW): 使用增量更新避免对象消失问题,修正并发标记期间改动的对象(需要STW,耗时比步骤1长,比步骤2短)
- 并发清除: 清理标记阶段判断已死亡的对象、重置状态等(该阶段也是并发执行)
- 适用场景:对延迟敏感的服务端应用。
- G1(Garbage-First)收集器
- 特点:
- 将堆划分为多个 Region,优先回收价值高的 Region(Garbage-First)。
- 结合标记-整理和复制算法,可预测停顿时间。
- 适用场景:大内存、低延迟应用(JDK 9+ 默认收集器)。
- 特点:
- ZGC 与 Shenandoah
- 特点:
- 超低延迟(STW 时间控制在 10ms 以内)。
- 基于并发标记和指针染色技术(如 ZGC 的染色指针)。
- 适用场景:超大堆内存(TB 级)和实时性要求极高的系统。
- 特点:
特殊说明:CMS和G1都是三色标记算法,在并发情况下三色标记会产生多标(浮动垃圾)和漏标问题,多标可以在下一次垃圾回收时再进行回收解决,漏标处理不当可能会造成空指针异常。
漏标产生的条件:一个黑节点指向了白色节点,灰色节点与白色节点断开了引用(二者条件必须同时存在,只要破坏其中一个条件即可解决漏标问题)。
CMS:通过写屏障+增量更新解决,原理是当黑节点B指向白节点E时,将B-->E记录到集合中,在重新标记阶段通过循环集合以B节点为起始节点重新标记。
G1:通过写屏障+SATB(原始快照)解决,原理是当灰色节点C与白色节点E断开引用时,将C-->E记录到集合中,在重新标记阶段通过循环集合以E节点为起始节点进行标记。
为了解决漏标,CMS不得不重新扫描从而造成性能下降,而G1由于从白色节点开始,假如没有活跃节点指向它,这时候就无法回收只能下次回收,可能会造成内存空间的浪费。
三、算法与收集器的选择
- 吞吐量优先:Parallel Scavenge + Parallel Old。
- 低延迟优先:G1、ZGC 或 Shenandoah。
- 内存限制:Serial 或 ParNew + CMS。
四、GC 优化建议
- 避免频繁创建短生命周期对象,减少 Minor GC。
- 合理设置堆大小(-Xms, -Xmx)和分代比例(-XX:NewRatio)。
- 根据应用特性选择收集器(如 -XX:+UseG1GC)。
- 监控 GC 日志(-Xlog:gc*)和分析工具(如 VisualVM、GCEasy)。
五、JDK默认垃圾回收器
1. JDK1.4 及更早版本
- 默认 GC:Serial GC(串行垃圾回收器),新生代和老年代均使用单线程回收。
2. JDK5 至 JDK8
- JDK5/JDK6:
- 服务端模式:新生代默认 Parallel Scavenge(并行回收),老年代默认 Serial Old(串行回收)。
- 客户端模式:全代使用 Serial GC。
- JDK7/JDK8:
- 服务端模式:默认组合升级为 Parallel Scavenge(新生代) + Parallel Old(老年代),全面支持多线程并行回收以提高吞吐量。
- 客户端模式:仍为 Serial GC。
3. JDK9 至 JDK21(最新版本)
- 默认 GC:G1(Garbage-First),统一管理新生代和老年代,通过分代 Region 设计平衡吞吐量与低延迟。
- 后续版本优化:
- JDK11+:支持 ZGC(超低停顿时间)和 Shenandoah(并发回收),但默认仍为 G1。
- JDK21:延续 G1 为默认,强化低延迟回收器的稳定性。
总结
- 早期版本(JDK1.4-6):以 Serial GC 和 Parallel Scavenge + Serial Old 为主,侧重简单性和单核性能。
- JDK7-8:转向 Parallel Scavenge + Parallel Old,优化多核环境下的吞吐量。
- JDK9 及之后:G1 成为主流默认,兼顾吞吐量与延迟,支持大内存和复杂应用场景。