Java垃圾回收的五十年——从标记清除到ZGC的演进之路

简介: 垃圾回收是Java平台的标志性特性,也是无数开发者选择Java而非C++的重要原因。

垃圾回收是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

目录
相关文章
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
32698 79
如何保证分布式文件系统的数据一致性
|
前端开发 容器
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局(上)
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局
17750 20
|
设计模式 存储 监控
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36682 19
设计模式(C++版)
|
存储 编译器 C语言
抽丝剥茧C语言(初阶 下)(下)
抽丝剥茧C语言(初阶 下)
|
机器学习/深度学习 人工智能 自然语言处理
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24758 14
|
机器学习/深度学习 弹性计算 监控
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36660 15
重生之---我测阿里云U1实例(通用算力型)
|
SQL 存储 弹性计算
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29838 52

热门文章

最新文章

下一篇
开通oss服务