前言:JVM GC收集器的回顾与比较
JVM(Java虚拟机)中的垃圾收集器是自动管理内存的重要机制,旨在回收不再使用的对象所占用的内存空间。以下是JVM中几种常见的垃圾收集器的详细介绍:
一、新生代垃圾收集器
-
1.Serial收集器
- 类型:单线程收集器,新生代。
- 特点:在垃圾收集时,会暂停所有其他工作线程(Stop-The-World),简单高效,发展历史最悠久。
- 适用场景:适用于单核处理器环境和Client模式下的虚拟机。
Serial收集器是Java虚拟机(JVM)中最基本、历史最悠久的垃圾收集器之一。
它是一个单线程的垃圾收集器,这意味着它只会使用一个线程来完成垃圾收集工作。
Serial收集器在进行垃圾收集时,必须暂停其他所有的工作线程
(称为“Stop The World”,简称STW),直到垃圾收集结束。
这种设计虽然简单高效,但会导致应用在垃圾收集期间完全停顿,影响用户体验
使用场景
Serial收集器主要用于客户端模式下的应用。由于客户端应用的内存通常较小,垃圾收集的时间较短,
只要不频繁发生停顿,这种设计是可以接受的。
此外,Serial收集器也是JDK 1.7及以前版本中Client模式下的默认新生代收集器
优缺点
优点:
简单高效:由于没有线程交互的开销,Serial收集器在单线程环境下可以获得较高的收集效率。
资源消耗少:对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,
专心做垃圾收集自然可以获得最高的单线程收集效率
缺点:
停顿时间长:在进行垃圾收集时,必须暂停所有其他工作线程,导致应用完全停顿,影响用户体验
不适用于多核环境:在多核环境下,由于只有一个线程工作,无法充分利用多核处理器的优势
-
2.ParNew收集器
- 类型:多线程收集器,新生代。
- 特点:ParNew收集器是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为与Serial收集器相同。
- 适用场景:适用于多核处理器环境。
ParNew是Java虚拟机(JVM)中的一个年轻代垃圾回收器,它是Serial收集器的多线程版本。
ParNew主要用于客户端应用或对响应时间敏感的应用程序,能够在多核处理器上发挥优势,
通过多线程并行处理垃圾回收工作,从而缩短Stop-The-World(STW)时间
工作原理
ParNew的主要工作原理包括以下几点:
并行性:ParNew使用多线程同时执行垃圾收集工作,
这使得它能够在多核处理器上发挥优势,缩短STW时间
分代收集:ParNew只关注年轻代的垃圾回收,不处理老年代的垃圾回收。
当年轻代空间不足时,ParNew会被触发
复制算法:在年轻代中采用复制算法,将年轻代分为Eden区和两个Survivor区(S0和S1)。
垃圾收集时,所有存活的对象会被复制到另一个Survivor区或老年代,原来的Survivor区则被清空
对象晋升:经过几次垃圾回收后,存活的对象会被晋升到老年代,以减少年轻代的垃圾回收频率
适用场景和优缺点
ParNew适用于以下场景:
响应时间敏感的应用程序:由于ParNew的多线程特性,它能够在短时间内完成垃圾回收,
减少STW时间,适合交互式应用程序
服务器端大型系统:在多核CPU服务器上,ParNew能够充分发挥硬件优势,
提升回收效率,适用于高并发场景
然而,ParNew也有一些局限性:
仅限于年轻代:ParNew不适用于整个堆的垃圾收集,只专注于年轻代
不适合高吞吐量应用:对于需要最大化CPU利用率的应用程序,ParNew可能不是最佳选择,
因为它的STW时间虽然较短,但总的垃圾收集时间可能较长
-
3.Parallel Scavenge收集器
- 类型:多线程收集器,新生代。
- 特点:追求高吞吐量,即CPU用于运行用户代码的时间与CPU总消耗时间的比值高。停顿时间越短越适合需要与用户交互的程序,而高吞吐量则可用高效率地利用CPU时间,尽快完成程序的运算任务。
- 适用场景:主要适合在后台运算而不需要太多交互的任务。
Parallel Scavenge收集器是JVM(Java虚拟机)中的一个垃圾收集器,主要用于新生代垃圾收集,
采用标记-复制算法。
Parallel Scavenge收集器是一款新生代收集器,主要关注垃圾收集的吞吐量。
吞吐量是指在一定时间内成功完成的工作量或任务的数量,即用户线程运行时间占总时间的比例。
Parallel Scavenge收集器通过多线程并行执行垃圾回收操作,提高吞吐量
Parallel Scavenge收集器的特点
并行处理:Parallel Scavenge收集器可以并行处理垃圾回收任务,利用多线程提高垃圾收集的效率。
关注吞吐量:其主要目标是优化系统的整体吞吐量,减少垃圾收集对系统性能的影响。
参数调节:提供了-XX:MaxGCPauseMillis参数,用于控制最大垃圾回收停顿时间,
通过牺牲部分吞吐量来减少停顿时间
Parallel Scavenge收集器的使用场景
Parallel Scavenge收集器适合那些对实时性要求不高,但需要高吞吐量的应用场景。
例如,科学数据计算、批量处理任务等。在这些场景下,虽然垃圾收集会占用一定的时间,
但总体上可以提高系统的处理效率
二、老年代垃圾收集器
-
1.Serial Old收集器
- 类型:单线程收集器,老年代。
- 特点:Serial Old是Serial收集器的老年代版本,使用标记整理算法。
- 适用场景:适用于单核处理器环境和Client模式下的虚拟机。
Serial Old收集器是JVM中用于老年代的垃圾收集器。
它是Serial收集器的老年代版本,同样采用单线程工作方式,
并且在垃圾收集时需要暂停所有其他工作线程(用户线程),直到垃圾收集完成,
这个过程被称为“Stop The World”(STW)
工作原理和特点
Serial Old收集器使用标记-压缩算法进行垃圾收集。它的主要用途包括:
与Parallel Scavenge收集器搭配使用:在JDK 1.5及以前的版本中,
Serial Old与Parallel Scavenge搭配使用,主要用于服务器端应用。
作为CMS收集器的后备方案:在CMS收集器无法及时完成垃圾收集时,
Serial Old作为后备方案进行老年代的垃圾收集
优缺点
优点:
简单高效:由于是单线程工作,实现简单,资源占用少。
适用于内存资源有限的环境:在客户端模式下,对于几十兆甚至一两百兆的新生代对象,
停顿时间可以控制在可接受范围内
缺点:
停顿时间长:由于需要暂停所有工作线程,导致应用在垃圾收集期间完全停顿,
影响用户体验和应用程序的响应时间
-
2.Parallel Old收集器
- 类型:多线程收集器,老年代。
- 特点:Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
- 适用场景:旨在提高系统吞吐量,适用于多线程环境。
Parallel Old收集器是JVM(Java虚拟机)中的一个垃圾收集器,主要用于老年代的垃圾收集。
它采用标记-压缩算法,并且是基于并行回收的机制。
Parallel Old收集器的主要特点是其高效的并行处理能力和对吞吐量的优化
特点
并行回收:Parallel Old收集器使用多个线程进行垃圾收集,提高了垃圾收集的效率。
标记-压缩算法:它使用标记-压缩算法来减少内存碎片,提高内存利用率。
Stop-the-World机制:在进行垃圾收集时,Parallel Old收集器会暂停所有用户线程(Stop-the-World),直到垃圾收集完成。
使用场景
Parallel Old收集器通常用于需要高吞吐量的应用场景。
它通过减少垃圾收集的停顿时间来优化整体的应用性能,适合长时间运行的大规模应用
-
3.CMS(Concurrent Mark Sweep)收集器 类型
-
定位:老年代并发收集器,需配合年轻代收集器(如ParNew)使用。
特点
-
目标:以降低停顿时间为核心目标,适合对延迟敏感的应用。
-
算法:采用**标记-清除(Mark-Sweep)**算法,非压缩式回收。
-
阶段划分:
-
初始标记(Initial Mark):标记GC Roots直接关联的老年代对象(需STW,时间短)。
-
并发标记(Concurrent Mark):遍历老年代对象图(与用户线程并发)。
-
重新标记(Remark):修正并发标记期间的变化(需STW,时间较长)。
-
并发清除(Concurrent Sweep):清理垃圾对象(与用户线程并发)。
-
优点
-
低停顿:通过并发标记和清除大幅减少STW时间。
-
适合交互式应用:如Web服务器、实时交易系统等需快速响应的场景。
缺点
-
CPU资源敏感:
-
并发阶段占用线程(默认启动
(CPU_Cores + 3)/4
个线程),在CPU核心数少(如≤4)时,可能导致应用吞吐量显著下降。
-
-
浮动垃圾(Floating Garbage):
-
并发清理阶段用户线程可能产生新垃圾,若空间不足会触发Concurrent Mode Failure,退化为Serial Old收集器进行Full GC(长时间STW)。
-
-
内存碎片:
-
标记-清除算法不整理内存,长期运行后碎片化严重,可能导致:
-
晋升失败(Promotion Failed):年轻代对象无法晋升到老年代。
-
即使老年代总空间足够,因碎片无法分配大对象而触发Full GC。
-
-
-
内存预留要求:
-
CMS需预留足够空间(通过
-XX:CMSInitiatingOccupancyFraction
设置触发阈值,默认92%),否则易触发并发失败。
-
-
JDK版本兼容性:
-
JDK 9+标记为废弃,JDK 14中正式移除,建议使用G1或ZGC替代。
-
适用场景
-
低延迟需求:要求GC停顿时间控制在几百毫秒以内。
-
中等堆内存(如4GB~8GB),且应用能容忍周期性Full GC。
-
CPU资源充足(建议≥4核),避免并发阶段争抢资源。
调优关键参数
参数 | 默认值 | 说明 |
---|---|---|
-XX:+UseConcMarkSweepGC | - | 启用CMS收集器 |
-XX:CMSInitiatingOccupancyFraction | 92% | 老年代使用率阈值触发CMS回收 |
-XX:+UseCMSCompactAtFullCollection | true | Full GC时进行内存压缩(解决碎片问题,但增加停顿时间) |
-XX:CMSFullGCsBeforeCompaction | 0 | 执行多少次Full GC后触发压缩(0表示每次Full GC都压缩) |
-XX:ParallelGCThreads | - | 设置并发阶段的GC线程数(通常设为CPU核心数的25%~30%) |
补充说明
-
与Full GC的关系:CMS的失败(如Concurrent Mode Failure)会触发Serial Old收集器进行Full GC,导致长时间停顿。
-
碎片问题缓解:可通过
-XX:+UseCMSCompactAtFullCollection
和-XX:CMSFullGCsBeforeCompaction
参数控制压缩频率,但会增加停顿时间。 -
监控指标:需关注
Concurrent Mode Failure
次数、Full GC
频率及耗时、老年代碎片率等。
总结
用户对CMS的描述基本正确,但需注意其已逐步被现代收集器(如G1、ZGC)取代,且存在内存碎片、并发失败等固有缺陷。在JDK 8及之前版本中,CMS仍适用于对延迟敏感的中等规模应用,但在新项目中建议优先考虑G1或低延迟的ZGC/Shenandoah。
CMS收集器(Concurrent Mark Sweep)是Java虚拟机(JVM)中的一种垃圾收集器,
主要用于老年代(Old Generation)的垃圾回收。
CMS收集器的目标是减少垃圾收集时的应用程序停顿时间(STW,Stop-The-World),
特别适用于对服务响应速度要求较高的场景,如互联网站或B/S系统的服务端Java应用
工作原理
CMS收集器使用“标记-清除”算法,其工作过程主要包括以下几个阶段:
初始标记:暂停所有其他线程,记录下GC Roots直接引用的对象。
并发标记:从GC Roots开始遍历整个对象图,这个过程可以与用户线程并发进行。
重新标记:修正并发标记期间因用户程序运行而导致的标记变化。
并发清理:开启用户线程,GC线程清理未标记的对象。
并发重置:重置本次GC过程中的标记数据
特点
并发性:CMS设计为并发低停顿的垃圾收集器,使得在垃圾回收过程中用户线程不会停顿或停顿时间很短,
有助于保障系统的响应速度
标记-清除算法:CMS使用标记-清除算法进行垃圾回收,
这种算法在处理过程中需要多次暂停用户线程以确保准确性
适用场景:由于CMS收集器能有效减少停顿时间,特别适合那些对用户体验要求较高的应用场景,
如服务器端应用和需要高并发处理的系统
三、兼顾新生代和老年代的垃圾收集器 G1收集器
-
G1(Garbage-First)收集器
- 类型:并行与并发收集器,适用于新生代和老年代。
- 特点:
- 并行与并发:G1收集器能够充分利用多核处理器的优势,通过并行执行垃圾收集任务来提高效率。同时,它的大部分工作都是与应用线程并发执行的,从而减少了停顿时间。
- 分区域收集:G1将整个堆内存划分为多个大小相等的独立区域(Region),这些区域在逻辑上是连续的,但在物理内存上可能不是连续的。每个Region都可以扮演Eden区、Survivor区或Old区等角色。这种设计使得G1 GC能够更加灵活地进行内存管理和垃圾收集。
- 优先回收垃圾最多区域:G1通过跟踪每个Region中的垃圾堆积情况,并根据回收价值和成本进行排序,优先回收垃圾最多的Region。这种策略有助于最大限度地提高垃圾收集的效率。
- 可预测的停顿时间:G1通过建立一个可预测的停顿时间模型,允许用户明确指定在一个特定时间片段内,垃圾收集所造成的停顿时间不得超过某个阈值。这使得G1非常适合需要严格控制停顿时间的应用场景。
- 适用场景:面向服务端应用,能充分利用多CPU、多核环境,适用于需要低延迟和可预测停顿时间的大型应用程序。
-
适合大堆内存
-
G1通过 Region分区机制(默认2048个分区)有效管理大内存堆(4GB~数十GB),规避传统分代GC的全堆扫描问题。
-
实际应用案例:在16GB以上堆内存场景中,G1的 平均停顿时间 通常比CMS低30%~50%。
-
-
简化调优
-
核心参数确实聚焦于:
-
-Xms
/-Xmx
(堆初始/最大值) -
-XX:MaxGCPauseMillis
(目标最大停顿时间,默认200ms)
-
-
例如,仅设置以下参数即可获得较好表现:
-XX:+UseG1GC -Xms8g -Xmx8g -XX:MaxGCPauseMillis=150
-
-
Garbage First设计原则
-
G1通过 回收效益优先模型(Predictive Model),优先回收垃圾比例高(Garbage-First)的Region,确保内存利用率合理。
-
统计表明,G1的 空间释放效率 比CMS高15%~25%,尤其适合对象生命周期差异大的场景。
-
-
进阶调优场景:
-
Region大小调整(
-XX:G1HeapRegionSize
):影响大对象分配和回收粒度。 -
并发线程数(
-XX:ConcGCThreads
):控制并发标记阶段的吞吐量。 -
混合回收策略(
-XX:G1MixedGCCountTarget
):调整Old Region回收比例。 -
示例调优参数:
-XX:G1HeapRegionSize=16m \ -XX:ConcGCThreads=4 \ -XX:G1MixedGCLiveThresholdPercent=85
-
-
G1收集器的历史背景和替代CMS收集器
G1收集器在JDK 7u4版本中被正式推出,并在JDK 9中成为默认的垃圾收集器。它取代了CMS收集器,后者在JDK 9中被废弃,并在JDK 14中被完全移除。G1的设计目的是为了适应不断扩大的内存和增加的处理器数量,进一步降低暂停时间,同时兼顾良好的吞吐量
-
总结:G1是一款面向大堆内存的垃圾收集器,通过Region分区和Garbage-First策略实现低停顿目标。其调优参数简单直观(堆大小和最大暂停时间),但需注意元数据内存开销。在JDK 11+中,G1通过算法优化显著提升了吞吐量和稳定性,成为多数场景的默认选择
性能数据参考
指标 | G1(JDK 17) | CMS(JDK 8) | ZGC(JDK 17) |
---|---|---|---|
最大堆内存支持 | 16TB | 16GB | 16TB |
平均停顿时间(ms) | 50~200 | 100~300 | 1~10 |
吞吐量损失 | 5%~10% | 10%~20% | <5% |
内存开销 | 中等 | 低 | 高 |
参考:详解 JVM Garbage First(G1) 垃圾收集器-腾讯云开发者社区-腾讯云