垃圾回收是Java平台的标志性特性,也是无数开发者选择Java而非C++的重要原因。从JDK 1.0到JDK 21,Java的垃圾回收算法经历了翻天覆地的变化,每一次演进都在吞吐量、延迟和内存占用之间寻找新的平衡。理解不同垃圾回收器的特点和工作原理,对于调优Java应用性能至关重要。
参考:https://xbivx.cn/category/provincial-forecast.html
标记-清除算法是最基础的垃圾回收算法。它分为两个阶段:标记阶段遍历所有存活对象,清除阶段回收未被标记的对象。这种算法的缺点是产生内存碎片,以及暂停时间与堆大小成正比。标记-清除的变种——标记-复制算法将内存分为两个半区,每次只使用其中一个,回收时将存活对象复制到另一个半区,然后清空原半区。复制算法无碎片,但内存利用率低。标记-整理算法在标记之后将存活对象向一端移动,然后清理边界以外的内存,既消除了碎片,又提高了内存利用率,但移动对象的开销较大。
分代假说是Java垃圾回收设计的理论基础。该假说指出:大部分对象朝生夕灭(年轻代),而老年代中的对象则长期存活。基于这个假说,JVM将堆内存分为年轻代和老年代。年轻代使用复制算法(因为大部分对象在GC中死亡),老年代使用标记-清除或标记-整理算法(因为存活率高,复制成本大)。
Serial GC是最古老的垃圾回收器,使用单线程进行垃圾回收。它会触发"Stop-The-World"暂停,所有应用线程停止,直到GC完成。Serial GC适合客户端应用或单处理器环境,堆内存较小(几十MB到几百MB)。它的优点是简单、内存占用低、没有线程同步开销。
Parallel GC(也称为吞吐量优先收集器)是JDK 8及之前版本的默认收集器。它使用多线程进行垃圾回收,充分利用多核CPU。Parallel GC的目标是最大化吞吐量(应用运行时间与总时间的比值)。它适用于后台处理、批处理等对暂停时间不敏感的场景。Parallel GC在年轻代使用并行复制,在老年代使用并行标记-整理。
CMS GC(Concurrent Mark Sweep)是第一款低延迟垃圾回收器。它的目标是减少暂停时间,将垃圾回收的大部分工作与应用线程并发执行。CMS的回收过程包括:初始标记(STW,标记GC Roots直接可达的对象)、并发标记(与应用线程一起标记所有存活对象)、重新标记(STW,处理并发标记期间发生变化的对象)、并发清除(清除未标记的对象)。CMS的缺点是产生内存碎片(因为不整理),且浮动垃圾(并发标记期间新产生的垃圾)需要等待下次GC。
G1 GC(Garbage First)在JDK 7u4中正式发布,在JDK 9中成为默认收集器。G1将堆内存划分为多个大小相等的Region,每个Region可以是年轻代或老年代。G1的回收策略是:优先回收垃圾最多的Region(Garbage First因此得名)。G1的目标是在可预测的暂停时间内回收尽可能多的内存。它使用停顿预测模型,动态调整年轻代大小和回收区域的数量。G1的回收过程包括:年轻代收集、并发标记周期、混合收集(逐步回收老年代Region)。G1避免了CMS的碎片问题,因为它在需要时对Region进行整理。
参考:https://xbivx.cn/category/national-weather.html
ZGC(Z Garbage Collector)在JDK 11中引入,在JDK 15中成为生产就绪。ZGC的目标是在任意堆大小(从几百MB到16TB)下,将暂停时间控制在10ms以内。ZGC使用读屏障和染色指针技术,将大部分GC工作与应用线程并发执行。ZGC的暂停只发生在根扫描阶段,且暂停时间不随堆大小增长。ZGC的设计挑战包括:处理大堆的并发标记和重定位、与操作系统内存管理的交互、以及JIT编译器的适配。ZGC适用于大堆内存、低延迟的场景,如金融交易系统、在线游戏服务器。
Shenandoah GC与ZGC类似,也是低延迟并发回收器,由Red Hat开发,在JDK 12中引入。与ZGC使用染色指针不同,Shenandoah使用 Brooks指针(转发指针)和读屏障实现并发压缩。Shenandoah的回收过程包括:初始标记、并发标记、并发清理、并发压缩等阶段。Shenandoah和ZGC代表了垃圾回收技术的前沿,它们将暂停时间从秒级降低到毫秒级甚至亚毫秒级。
Epsilon GC是JDK 11中引入的无操作垃圾回收器。它分配内存但不回收,当堆内存耗尽时JVM退出。Epsilon适用于短期任务、性能测试、或完全由应用管理内存的场景。
GC调优是Java性能工程的重要部分。调优的步骤包括:明确目标(吞吐量优先还是延迟优先)、监控GC日志(使用-Xlog:gc*)、选择合适的垃圾回收器、调整堆大小(-Xms和-Xmx)、调整年轻代与老年代比例(-XX:NewRatio)、调整晋升阈值(-XX:MaxTenuringThreshold)等。过度调优往往是反模式——在大多数情况下,默认配置已经足够好。
GC日志分析工具可以帮助理解GC行为。GCViewer、GCEasy、JMC等工具可以可视化GC事件,分析暂停时间、吞吐量、晋升速率等指标。现代JVM还提供了JFR(Java Flight Recorder)事件,可以低开销地采集GC信息。
未来趋势:Project Lilliput(JDK 21预览)尝试缩减对象头大小,减少内存占用;Project Valhalla的值类型将进一步减少对象数量,降低GC压力;分代ZGC(JDK 21正式)将年轻代和老年代分离,进一步提升ZGC的性能。
垃圾回收不是魔法,而是一套精心设计的算法和工程实现。理解GC的工作原理,不仅有助于调优,还能帮助开发者写出对GC友好的代码:避免对象晋升、避免大对象分配、使用对象池复用对象、以及及时释放引用。
参考:https://xbivx.cn