Java垃圾收集器全解:从Serial到G1的进化之旅

在Java的世界里,垃圾收集器就像是默默无闻的清洁工,在我们不注意的时候悄悄清理内存垃圾。不同的清洁工有不同的工作方式,有的喜欢一次性彻底打扫(Stop The World),有的则喜欢边工作边让你继续玩耍(并发收集)。今天,就让我们一起来认识这些各具特色的"清洁工"吧!

收集器家族一览

新生代收集器

1. Serial收集器 - "单干的老黄牛"

工作方式 :单线程,Stop The World算法 :标记-复制适用场景:客户端模式,内存受限环境

Serial收集器就像是一位勤劳的清洁阿姨,每次打扫时都需要你暂时离开房间(Stop The World)。她工作认真,但没有帮手,只能一件一件地清理。

详细介绍:Serial收集器是Java最古老的新生代收集器,采用单线程执行垃圾回收工作。当它工作时,会暂停所有应用线程(Stop The World),直到收集完成。这种"全世界暂停"的方式虽然简单粗暴,但在特定场景下却非常有效。

核心特点

  • 单线程工作,收集时暂停所有应用线程

  • 采用标记-复制算法,将Eden区和From Survivor区的存活对象复制到To Survivor区

  • 额外内存消耗最小,只需要维护一个线程的开销

  • 在单核处理器环境下效率很高,没有线程交互开销

配置参数

  • -XX:+UseSerialGC:启用Serial收集器

  • -XX:SurvivorRatio:Eden与Survivor区的比例

  • -XX:PretenureSizeThreshold:大对象直接进入老年代的阈值

适用场景

  • 客户端应用,如桌面程序、小程序

  • 小内存环境(几十到几百MB的新生代)

  • 单核或双核处理器环境

  • 对停顿时间不敏感的应用

优缺点

  • ✅ 优点:实现简单、内存开销小、单核环境下效率高

  • ❌ 缺点:停顿时间长、在多核环境下无法充分利用CPU资源

2. ParNew收集器 - "多人协作的清洁队"

工作方式 :多线程,Stop The World算法 :标记-复制适用场景:与CMS配合的服务端应用

ParNew就像是Serial的升级版,从单人作战变成了团队协作。但它仍然需要在工作时让所有人暂时离开房间。

详细介绍:ParNew收集器是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其他行为与Serial收集器完全一致。它与Serial收集器共用了大量的代码,可以看作是Serial的并行化改造。

核心特点

  • 多线程并行收集,默认线程数与CPU核心数相同

  • 采用与Serial相同的标记-复制算法

  • 需要Stop The World,但停顿时间比Serial短

  • 与CMS收集器配合工作

配置参数

  • -XX:+UseParNewGC:启用ParNew收集器

  • -XX:ParallelGCThreads:设置垃圾收集线程数

  • -XX:SurvivorRatio:Eden与Survivor区的比例

适用场景

  • 与CMS收集器配合使用

  • 多核处理器的服务端应用

  • 对响应时间有一定要求的应用

工作流程

  1. 暂停所有应用线程(Stop The World)

  2. 多线程并行标记存活对象

  3. 多线程并行复制存活对象到Survivor区

  4. 清理Eden区和From Survivor区

  5. 恢复应用线程

优缺点

  • ✅ 优点:多线程并行收集,停顿时间比Serial短

  • ❌ 缺点:仍然需要Stop The World,单核环境下性能不如Serial

3. Parallel Scavenge收集器 - "效率至上的工厂流水线"

工作方式 :多线程,Stop The World算法 :标记-复制适用场景:后台运算,吞吐量优先应用

Parallel Scavenge不像是在打扫房间,更像是在优化工厂生产线。它不关心每次停顿多久,只关心单位时间内能完成多少工作。

详细介绍:Parallel Scavenge收集器是一款专注于吞吐量的新生代收集器。它的目标是达到一个可控制的吞吐量(Throughput),即处理器用于运行用户代码的时间与处理器总消耗时间的比值。

核心特点

  • 关注点是吞吐量而非停顿时间

  • 提供精确控制吞吐量的参数

  • 支持自适应调节策略

  • 采用多线程并行收集

配置参数

  • -XX:+UseParallelGC:启用Parallel Scavenge收集器

  • -XX:MaxGCPauseMillis:最大垃圾收集停顿时间

  • -XX:GCTimeRatio:垃圾收集时间与总时间的比率

  • -XX:+UseAdaptiveSizePolicy:启用自适应策略

自适应策略:当启用自适应策略后,虚拟机会根据系统运行情况自动调整以下参数:

  • 新生代大小(-Xmn)

  • Eden与Survivor区的比例(-XX:SurvivorRatio)

  • 晋升老年代对象大小(-XX:PretenureSizeThreshold)

适用场景

  • 后台运算任务,如批量处理、数据分析

  • 对吞吐量要求高于响应时间的应用

  • 不需要与用户交互的应用

优缺点

  • ✅ 优点:吞吐量高、支持自适应调节、适合后台运算

  • ❌ 缺点:停顿时间不可控、不适合交互式应用

老年代收集器

4. Serial Old收集器 - "老黄牛的晚年生活"

工作方式 :单线程,Stop The World算法 :标记-整理适用场景:客户端模式,CMS失败时的备胎

Serial Old就像是Serial收集器退休后再就业,虽然年纪大了,但依然兢兢业业。

详细介绍:Serial Old是Serial收集器的老年代版本,同样采用单线程工作和Stop The World的方式。它使用标记-整理算法,避免内存碎片的产生。

核心特点

  • 单线程工作,需要Stop The World

  • 采用标记-整理算法

  • 内存开销小

  • 作为CMS失败时的备胎

工作流程

  1. 标记阶段:标记所有存活对象

  2. 整理阶段:将所有存活对象向一端移动

  3. 清理阶段:清理边界以外的内存

适用场景

  • 客户端模式下的老年代收集

  • 与Parallel Scavenge搭配使用(JDK5及之前)

  • CMS收集器发生Concurrent Mode Failure时的后备预案

配置参数

  • -XX:+UseSerialGC:启用Serial Old收集器

  • 通常与其他收集器配合使用,不需要单独配置

优缺点

  • ✅ 优点:实现简单、内存开销小、不会产生内存碎片

  • ❌ 缺点:停顿时间长、单线程效率低

5. Parallel Old收集器 - "流水线的完美搭档"

工作方式 :多线程,Stop The World算法 :标记-整理适用场景:与Parallel Scavenge搭配的吞吐量优先应用

Parallel Old的出现让Parallel Scavenge终于有了门当户对的搭档,形成了真正的"吞吐量优先"组合。

详细介绍:Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。它在JDK6中才开始提供,填补了Parallel Scavenge没有匹配老年代收集器的空白。

核心特点

  • 多线程并行收集

  • 采用标记-整理算法

  • 专注于吞吐量优化

  • 与Parallel Scavenge完美配合

工作流程

  1. 标记阶段:多线程并行标记存活对象

  2. 整理阶段:多线程并行整理内存

  3. 清理阶段:清理不可达对象

适用场景

  • 与Parallel Scavenge搭配使用

  • 注重吞吐量的应用

  • 后台运算任务

配置参数

  • -XX:+UseParallelOldGC:启用Parallel Old收集器

  • 通常与Parallel Scavenge一起使用

优缺点

  • ✅ 优点:吞吐量高、多线程并行、与Parallel Scavenge配合良好

  • ❌ 缺点:停顿时间较长、不适合交互式应用

6. CMS收集器 - "边开派对边打扫的管家"

工作方式 :并发,低停顿算法 :标记-清除适用场景:B/S系统,重视响应速度的应用

CMS就像是一位技艺高超的管家,能够在派对进行中悄悄打扫,尽量不打扰宾客的雅兴。

详细介绍:CMS(Concurrent Mark Sweep)收集器是一款以获取最短回收停顿时间为目标的收集器,非常适合重视响应速度的应用。它采用标记-清除算法,尽可能减少Stop The World的时间。

工作流程

  1. 初始标记:标记GC Roots直接关联的对象(需要Stop The World,但很快)

  2. 并发标记:并发遍历对象图,标记所有可达对象

  3. 重新标记:修正并发标记期间变动的标记记录(需要Stop The World)

  4. 并发清除:并发清理垃圾对象

核心特点

  • 并发收集,低停顿时间

  • 采用标记-清除算法,会产生内存碎片

  • 对处理器资源敏感

  • 无法处理"浮动垃圾"

配置参数

  • -XX:+UseConcMarkSweepGC:启用CMS收集器

  • -XX:CMSInitiatingOccupancyFraction:老年代使用率触发阈值

  • -XX:+UseCMSCompactAtFullCollection:Full GC时开启内存压缩

  • -XX:CMSFullGCsBeforeCompaction:设置多少次Full GC后压缩一次

浮动垃圾问题:由于CMS的并发清理阶段用户线程仍在运行,会产生新的垃圾对象,这些对象无法在本次收集中处理,称为"浮动垃圾"。

适用场景

  • B/S系统服务端

  • 重视响应速度的应用

  • 互联网应用

优缺点

  • ✅ 优点:并发收集、低停顿时间、适合交互式应用

  • ❌ 缺点:对CPU资源敏感、会产生内存碎片、无法处理浮动垃圾

全堆收集器:G1 - "智能分区清洁系统"

工作方式 :并发+并行算法 :整体标记-整理,局部标记-复制适用场景:大内存服务端应用,兼顾吞吐量和停顿时间

G1收集器就像是现代智能清洁系统,将房间分成多个区域,每次只清理最脏的区域,并且能够预测清理需要的时间。

详细介绍:G1(Garbage First)收集器是垃圾收集器技术发展历史上的里程碑成果,开创了面向局部收集的设计思路和基于Region的内存布局形式。它旨在替代CMS收集器,提供更可控的停顿时间。

革命性创新

  1. 基于Region的堆内存布局:将堆划分为多个大小相等的Region

  2. 可预测的停顿时间模型 :通过-XX:MaxGCPauseMillis参数设定期望停顿时间

  3. 面向局部收集:不再坚持固定分代,而是选择回收价值最大的Region

  4. Humongous区域:专门处理大对象

工作流程

  1. 初始标记:标记GC Roots直接关联的对象

  2. 并发标记:并发进行可达性分析

  3. 最终标记:处理并发标记期间的引用变动

  4. 筛选回收:根据回收价值排序,选择Region进行回收

配置参数

  • -XX:+UseG1GC:启用G1收集器

  • -XX:MaxGCPauseMillis:设置最大停顿时间目标

  • -XX:G1HeapRegionSize:设置Region大小

  • -XX:InitiatingHeapOccupancyPercent:触发并发标记周期的Java堆占用率阈值

适用场景

  • 大内存服务端应用(6GB以上)

  • 需要兼顾吞吐量和停顿时间的应用

  • 替代CMS收集器

优缺点

  • ✅ 优点:停顿时间可控、高吞吐量、不会产生内存碎片

  • ❌ 缺点:内存占用较高、写屏障实现复杂

如何选择收集器?

选择垃圾收集器就像选择清洁方式,需要根据实际情况决定:

  1. 小内存客户端应用(小于512MB)

    Serial + Serial Old组合

    简单高效,内存开销小

  2. 重视响应速度的B/S系统

    ParNew + CMS组合

    低停顿时间,适合交互式应用

  3. 后台运算,注重吞吐量

    Parallel Scavenge + Parallel Old组合

    高吞吐量,适合批处理任务

  4. 大内存服务端应用(大于6GB)

    G1收集器

    兼顾吞吐量和停顿时间

  5. 超大内存应用(大于32GB)

    考虑ZGC或Shenandoah

    极低停顿时间,适合超大内存

选择建议

  • 先明确应用需求:吞吐量优先还是响应时间优先

  • 根据堆内存大小选择

  • 在生产环境中进行实际测试验证

  • 随着JDK版本更新,优先考虑 newer 收集器

结语

Java垃圾收集器的发展历程就像清洁方式的进化:从最初需要全员离开的彻底打扫(Serial),到可以边工作边打扫的智能清洁(CMS),再到分区打扫、时间可控的现代清洁系统(G1)。每种收集器都有其适用场景,没有绝对的最好,只有最适合的。

随着JDK的持续发展,更新的收集器如ZGC、Shenandoah也在不断涌现,它们提供了更低的停顿时间和更好的性能表现。但理解这些基础收集器的工作原理,仍然是我们优化Java应用性能的基石。

希望本文能帮助你更好地理解Java垃圾收集器,选择适合的"清洁工",让你的Java应用跑得更加顺畅!记住,垃圾收集器的调优是一个持续的过程,需要根据实际应用特点和负载变化进行调整。

文章转载自: ++佛祖让我来巡山++

原文链接: https://www.cnblogs.com/sun-10387834/p/19094842

体验地址: http://www.jnpfsoft.com/?from=001YH

相关推荐
树在风中摇曳2 小时前
Java 静态成员与继承封装实战:从报错到彻底吃透核心特性
java·开发语言
百锦再4 小时前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
芳草萋萋鹦鹉洲哦4 小时前
【Windows】tauri+rust运行打包工具链安装
开发语言·windows·rust
权泽谦4 小时前
R Shiny 交互式网页实战:从零到上线可视化应用
开发语言·信息可视化·r语言
hweiyu005 小时前
Go Fiber 简介
开发语言·后端·golang
键来大师5 小时前
Android15 RK3588 修改默认不锁屏不休眠
android·java·framework·rk3588
zbhbbedp282793cl6 小时前
如何在VSCode中安装Python扩展?
ide·vscode·python
合作小小程序员小小店6 小时前
web网页开发,在线%考试管理%系统,基于Idea,vscode,html,css,vue,java,maven,springboot,mysql
java·前端·系统架构·vue·intellij-idea·springboot
多多*7 小时前
maven常用的命令
java·log4j·maven