----------------- 先赞后看 👍 效果翻倍 🔥 -----------------
概述
在Java的世界里,垃圾收集器就像是默默无闻的清洁工,在我们不注意的时候悄悄清理内存垃圾。不同的清洁工有不同的工作方式,有的喜欢一次性彻底打扫(Stop The World),有的则喜欢边工作边让你继续玩耍(并发收集)。今天,就让我们一起来认识这些各具特色的"清洁工"吧!
收集器家族一览
收集器 | 工作方式 | 适用分代 | 特点 | 适用场景 |
---|---|---|---|---|
Serial | 单线程 | 新生代 | 简单高效,停顿时间长 | 客户端应用,小内存 |
ParNew | 多线程 | 新生代 | Serial的多线程版本 | 与CMS配合 |
Parallel Scavenge | 多线程 | 新生代 | 吞吐量优先 | 后台运算,不注重交互 |
Serial Old | 单线程 | 老年代 | Serial的老年代版本 | 客户端,CMS备胎 |
Parallel Old | 多线程 | 老年代 | Parallel Scavenge的老年代版 | 吞吐量优先应用 |
CMS | 并发 | 老年代 | 低停顿时间 | B/S系统,重视响应 |
G1 | 并发+并行 | 全堆 | 可控停顿时间 | 大内存服务端应用 |
新生代收集器
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收集器配合使用
- 多核处理器的服务端应用
- 对响应时间有一定要求的应用
工作流程:
- 暂停所有应用线程(Stop The World)
- 多线程并行标记存活对象
- 多线程并行复制存活对象到Survivor区
- 清理Eden区和From Survivor区
- 恢复应用线程
优缺点:
- ✅ 优点:多线程并行收集,停顿时间比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失败时的备胎
工作流程:
- 标记阶段:标记所有存活对象
- 整理阶段:将所有存活对象向一端移动
- 清理阶段:清理边界以外的内存
适用场景:
- 客户端模式下的老年代收集
- 与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完美配合
工作流程:
- 标记阶段:多线程并行标记存活对象
- 整理阶段:多线程并行整理内存
- 清理阶段:清理不可达对象
适用场景:
- 与Parallel Scavenge搭配使用
- 注重吞吐量的应用
- 后台运算任务
配置参数:
-XX:+UseParallelOldGC
:启用Parallel Old收集器- 通常与Parallel Scavenge一起使用
优缺点:
- ✅ 优点:吞吐量高、多线程并行、与Parallel Scavenge配合良好
- ❌ 缺点:停顿时间较长、不适合交互式应用
6. CMS收集器 - "边开派对边打扫的管家"
工作方式 :并发,低停顿
算法 :标记-清除
适用场景:B/S系统,重视响应速度的应用
CMS就像是一位技艺高超的管家,能够在派对进行中悄悄打扫,尽量不打扰宾客的雅兴。
详细介绍 :
CMS(Concurrent Mark Sweep)收集器是一款以获取最短回收停顿时间为目标的收集器,非常适合重视响应速度的应用。它采用标记-清除算法,尽可能减少Stop The World的时间。
工作流程:
- 初始标记:标记GC Roots直接关联的对象(需要Stop The World,但很快)
- 并发标记:并发遍历对象图,标记所有可达对象
- 重新标记:修正并发标记期间变动的标记记录(需要Stop The World)
- 并发清除:并发清理垃圾对象
核心特点:
- 并发收集,低停顿时间
- 采用标记-清除算法,会产生内存碎片
- 对处理器资源敏感
- 无法处理"浮动垃圾"
配置参数:
-XX:+UseConcMarkSweepGC
:启用CMS收集器-XX:CMSInitiatingOccupancyFraction
:老年代使用率触发阈值-XX:+UseCMSCompactAtFullCollection
:Full GC时开启内存压缩-XX:CMSFullGCsBeforeCompaction
:设置多少次Full GC后压缩一次
浮动垃圾问题 :
由于CMS的并发清理阶段用户线程仍在运行,会产生新的垃圾对象,这些对象无法在本次收集中处理,称为"浮动垃圾"。
适用场景:
- B/S系统服务端
- 重视响应速度的应用
- 互联网应用
优缺点:
- ✅ 优点:并发收集、低停顿时间、适合交互式应用
- ❌ 缺点:对CPU资源敏感、会产生内存碎片、无法处理浮动垃圾
全堆收集器:G1 - "智能分区清洁系统"
工作方式 :并发+并行
算法 :整体标记-整理,局部标记-复制
适用场景:大内存服务端应用,兼顾吞吐量和停顿时间
G1收集器就像是现代智能清洁系统,将房间分成多个区域,每次只清理最脏的区域,并且能够预测清理需要的时间。
详细介绍 :
G1(Garbage First)收集器是垃圾收集器技术发展历史上的里程碑成果,开创了面向局部收集的设计思路和基于Region的内存布局形式。它旨在替代CMS收集器,提供更可控的停顿时间。
革命性创新:
- 基于Region的堆内存布局:将堆划分为多个大小相等的Region
- 可预测的停顿时间模型 :通过
-XX:MaxGCPauseMillis
参数设定期望停顿时间 - 面向局部收集:不再坚持固定分代,而是选择回收价值最大的Region
- Humongous区域:专门处理大对象
工作流程:
- 初始标记:标记GC Roots直接关联的对象
- 并发标记:并发进行可达性分析
- 最终标记:处理并发标记期间的引用变动
- 筛选回收:根据回收价值排序,选择Region进行回收
配置参数:
-XX:+UseG1GC
:启用G1收集器-XX:MaxGCPauseMillis
:设置最大停顿时间目标-XX:G1HeapRegionSize
:设置Region大小-XX:InitiatingHeapOccupancyPercent
:触发并发标记周期的Java堆占用率阈值
适用场景:
- 大内存服务端应用(6GB以上)
- 需要兼顾吞吐量和停顿时间的应用
- 替代CMS收集器
优缺点:
- ✅ 优点:停顿时间可控、高吞吐量、不会产生内存碎片
- ❌ 缺点:内存占用较高、写屏障实现复杂
如何选择收集器?
选择垃圾收集器就像选择清洁方式,需要根据实际情况决定:
-
小内存客户端应用(小于512MB)
- Serial + Serial Old组合
- 简单高效,内存开销小
-
重视响应速度的B/S系统
- ParNew + CMS组合
- 低停顿时间,适合交互式应用
-
后台运算,注重吞吐量
- Parallel Scavenge + Parallel Old组合
- 高吞吐量,适合批处理任务
-
大内存服务端应用(大于6GB)
- G1收集器
- 兼顾吞吐量和停顿时间
-
超大内存应用(大于32GB)
- 考虑ZGC或Shenandoah
- 极低停顿时间,适合超大内存
选择建议:
- 先明确应用需求:吞吐量优先还是响应时间优先
- 根据堆内存大小选择
- 在生产环境中进行实际测试验证
- 随着JDK版本更新,优先考虑 newer 收集器
结语
Java垃圾收集器的发展历程就像清洁方式的进化:从最初需要全员离开的彻底打扫(Serial),到可以边工作边打扫的智能清洁(CMS),再到分区打扫、时间可控的现代清洁系统(G1)。每种收集器都有其适用场景,没有绝对的最好,只有最适合的。
随着JDK的持续发展,更新的收集器如ZGC、Shenandoah也在不断涌现,它们提供了更低的停顿时间和更好的性能表现。但理解这些基础收集器的工作原理,仍然是我们优化Java应用性能的基石。
希望本文能帮助你更好地理解Java垃圾收集器,选择适合的"清洁工",让你的Java应用跑得更加顺畅!记住,垃圾收集器的调优是一个持续的过程,需要根据实际应用特点和负载变化进行调整。