Java虚拟机:并行和并发回收器
一、引言为什么需要了解垃圾回收器Java程序员之所以能够从手动内存管理中解放出来全靠JVM的垃圾回收Garbage Collection, GC机制。然而「自动」不等于「免费」——GC带来的停顿Stop-The-World, STW往往是系统延迟的罪魁祸首。选择合适的垃圾回收器就像为你的应用选择一辆合适的车跑车追求速度SUV追求空间卡车追求载重。没有最好的回收器只有最适合你业务场景的回收器。本文将从新生代回收器入手详细讲解ParNew和ParallelGC并顺带介绍与其配合的老年代回收器帮助你建立起完整的知识体系。二、前置知识GC的基础概念在深入具体回收器之前先快速回顾几个关键概念2.1 分代假说与堆内存划分JVM将堆内存分为新生代Young Generation和老年代Old Generation新生代对象“朝生夕死”适合用复制算法回收频繁但速度快。老年代对象存活时间长适合用标记-整理压缩算法回收次数少但单次耗时较长。新生代内部又分为Eden区和两个Survivor区S0, S1比例默认为8:1:1。2.2 Safepoint与Stop-The-WorldSafepoint安全点程序执行过程中的一个特殊位置线程可以在此暂停。GC时所有应用线程必须到达Safepoint后才能被挂起。STWStop-The-WorldGC期间暂停所有用户线程的现象。STW是GC调优的核心痛点我们所有努力都是为了在「吞吐量」和「延迟」之间找平衡。三、新生代回收器ParNew3.1 什么是ParNewParNew是新生代并行多线程回收器。本质上它是串行回收器Serial的多线程版本——回收算法复制算法、内存布局、参数配置几乎一模一样唯一的区别就是使用多个GC线程并行工作。如图所示多个GC线程同时工作但期间所有用户线程App Threads全部暂停属于独占式回收器。3.2 ParNew的核心特点特性说明算法复制算法新生代标配并行性多线程并行执行GC任务独占性GC期间STW暂停所有应用线程适用场景多核CPU环境常与CMS配合使用3.3 何时应该使用ParNew✅最常见场景与老年代的CMSConcurrent Mark Sweep回收器配合使用。CMS是一款以「低延迟」为目标的回收器而ParNew是其默认的新生代搭档。✅Server模式默认很多JVM在Server模式下新生代默认就是ParNew当启用CMS时自动激活。⚠️不适合单核/弱并发CPU多线程上下文切换的开销可能让性能不升反降。3.4 启用参数与线程控制# 方式一新生代使用ParNew老年代使用串行回收器不推荐 -XX:UseParNewGC # 方式二新生代使用ParNew老年代使用CMS最常见组合 -XX:UseConcMarkSweepGC # 该参数会自动启用ParNew线程数控制-XX:ParallelGCThreadsN # 指定GC线程数默认情况下CPU 8线程数 CPU核心数CPU ≥ 8线程数 3 (5 × CPU核心数) / 8调优建议不建议手动调整ParallelGCThreads除非你非常清楚CPU拓扑结构。过多线程反而会增加竞争和调度开销。3.5 ParNew日志解读0.834: [GC 0.834: [ParNew: 13184K-1600K(14784K), 0.0092203 secs] 13184K-1921K(63936K), 0.0093401 secs] [Times: user0.00 sys0.00, real0.00 secs]逐字段解读0.834GC发生的时间点从JVM启动开始计算[ParNew: 13184K-1600K(14784K)]新生代回收前占用13184K →回收后1600K括号内是新生代总容量14784K13184K-1921K(63936K)整个堆回收前→回收后容量总堆大小63936K0.0093401 secs本次GC总耗时[Times: user0.00 sys0.00, real0.00 secs]user用户态CPU时间sys内核态CPU时间real墙钟时间实际经过时间注意ParNew的日志格式和串行回收器几乎一样只是回收器标识从DefNew变成了ParNew。四、新生代回收器ParallelGC吞吐量优先4.1 什么是ParallelGC新生代ParallelGC回收器也叫PSYoungGen同样是使用复制算法的并行回收器。表面上看和ParNew很像但它们的设计目标截然不同ParNew为了配合CMS追求低延迟ParallelGC为了最大化吞吐量如图所示应用程序线程与GC线程交替运行GC期间仍然需要STW但GC线程并行执行缩短停顿时间。4.2 吞吐量优先是什么意思吞吐量计算公式吞吐量运行用户代码的时间/运行用户代码时间垃圾收集时间假设程序运行100分钟GC耗时1分钟则吞吐量为99%。高吞吐量意味着CPU时间被高效利用于业务逻辑适合后台计算、批处理、科学计算等对交互延迟不敏感的场景。4.3 ParallelGC的核心参数参数一-XX:MaxGCPauseMillisN目标设置最大GC停顿时间毫秒。设置后JVM会动态调整堆大小、新生代比例等参数尽量让每次GC停顿低于此值。⚠️陷阱如果你把值设得过小比如5msJVM为了达成目标可能会缩小堆内存导致GC更频繁反而降低吞吐量。这是软目标并非绝对保证。参数二-XX:GCTimeRatioN目标控制GC时间占总时间的比例。GCTimeRatio n表示GC时间 / (业务时间 GC时间) ≤ 1/(n1)默认值99即GC时间 ≤ 1%例如设为19则GC时间 ≤ 1/20 5%参数三自适应GC策略-XX:UseAdaptiveSizePolicy这是ParallelGC的杀手锏。开启后JVM会自动调节新生代大小Eden与Survivor比例晋升老年代的年龄阈值让虚拟机自己找到堆大小、吞吐量、停顿时间之间的平衡点。在手工调优困难的场景下这是“懒人福音”。-XX:UseAdaptiveSizePolicy # 默认开启UseParallelGC下4.4 启用方式与组合# 方式一新生代用ParallelGC老年代用ParallelOldGC -XX:UseParallelGC # 方式二效果同上互相激活 -XX:UseParallelOldGC# 两者任选一个另一个会自动启用这是吞吐量优先组合在JDK8中这是Server模式的默认回收器组合。4.5 ParallelGC日志解读text[GC (Allocation Failure) [PSYoungGen: 1076K-400K(2560K)] 6534K-5858K(9728K), 0.0007954 secs] [Times: user0.00 sys0.00, real0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 400K-0K(1536K)] [ParOldGen: 5458K-2773K(7168K)] 5858K-2773K(8704K), 0.0020814 secs]关键标识PSYoungGen新生代ParallelGCParOldGen老年代ParallelOldGCFull GC表示本次是Full GC会同时回收新生代和老年代五、老年代回收器ParallelOldGC5.1 什么是ParallelOldGCParallelOldGC是老年代的多线程回收器与新生代ParallelGC配对使用。它采用标记-整理压缩算法避免内存碎片。https://image.png5.2 核心特点特性说明算法标记-整理压缩并行性多线程并行执行设计目标吞吐量优先搭档新生代ParallelGC5.3 启用方式bash-XX:UseParallelOldGC # 新生代自动使用ParallelGC # 或 -XX:UseParallelGC # 老年代自动使用ParallelOldGC5.4 完整日志示例text1.500: [Full GC [PSYoungGen: 2682K-0K(19136K)] [ParOldGen: 28035K-30437K(43712K)] 30717K-30437K(62848K) [PSPermGen: 10943K-10928K(32768K)], 0.2902791 secs] [Times: user1.44 sys0.03, real0.30 secs]解读新生代从2682K→0K清空老年代从28035K→30437K整理后容量微增因为新生代对象晋升了整个堆从30717K→30437K永久代JDK8之前也发生了轻微回收user1.44多线程CPU总耗时1.44s而real0.30s说明并行效果显著六、ParNew vs ParallelGC一张表看懂区别对比维度ParNewParallelGC设计目标低延迟配合CMS高吞吐量算法复制算法复制算法并行线程✅ 多线程✅ 多线程自适应调节❌ 不支持✅ 支持UseAdaptiveSizePolicy停顿控制参数无专门参数MaxGCPauseMillis / GCTimeRatio老年代搭档Serial Old / CMSParallelOldGC典型应用场景Web服务、交互式应用后台计算、批处理、大数据JDK8默认组合非默认需配合CMSServer模式默认七、Safepoint与STW的底层原理7.1 什么是SafepointSafepoint是JVM中的一个特殊状态线程执行到这里时可以安全地暂停。GC发生时所有线程必须同时到达Safepoint。https://image.png7.2 为什么需要STW以复制算法为例GC线程需要移动存活对象更新所有引用。如果在移动过程中应用线程同时在修改引用会导致对象丢失或重复。因此必须STW保证一致性。7.3 CMS如何减少STWCMSConcurrent Mark Sweep将STW阶段尽量压缩让大部分标记和清理工作与应用线程并发执行。https://image.pngCMS的STW阶段只有初始标记Initial Mark标记根对象STW短暂重新标记Remark修正并发标记阶段变动的对象STW较短暂而并发标记、并发预清理、并发清除阶段都不需要STW应用线程可以继续运行。这也是ParNew常与CMS搭配的原因——新生代STW时间极短老年代几乎不STW整体延迟很低。八、实战调优指南8.1 如何选择回收器应用类型推荐组合参数低延迟Web服务ParNew CMS-XX:UseConcMarkSweepGC高吞吐量计算ParallelGC ParallelOldGC-XX:UseParallelGC小内存/单核Serial SerialOld默认无需额外参数8.2 常见调优场景场景1GC频繁导致吞吐量下降# 增大堆内存减少GC频率 -Xmx2g -Xms2g # 调整GC时间占比 -XX:GCTimeRatio95 # GC时间控制在5%以内场景2GC停顿时间过长# 设置停顿目标谨慎使用可能降低吞吐量 -XX:MaxGCPauseMillis100 # 开启自适应策略ParallelGC下默认开启 -XX:UseAdaptiveSizePolicy场景3CPU核心数多但GC并行度不够# 增加GC线程谨慎过多反而有害 -XX:ParallelGCThreads88.3 日志分析要点关注Full GC频率Full GC频率过高通常是内存不足或内存泄漏的信号。关注promotion failed表示新生代对象晋升老年代失败通常是因为老年代空间不足或碎片过多。关注user/real比例如果user远大于real × 核心数说明GC线程可能存在竞争或阻塞。九、总结回收器所属代算法并行目标搭档ParNew新生代复制✅低延迟CMS / Serial OldParallelGC新生代复制✅高吞吐量ParallelOldGCParallelOldGC老年代标记-整理✅高吞吐量ParallelGC核心要点回顾ParNew是串行回收器的多线程版专为配合CMS设计适合多核、低延迟场景。ParallelGC是吞吐量优先的回收器提供MaxGCPauseMillis和GCTimeRatio两个控制参数并支持自适应调节。ParallelOldGC是老年代吞吐量优先回收器与ParallelGC组成黄金搭档是JDK8 Server模式的默认选择。STW是GC的必然代价不同回收器的区别在于如何缩短STW时间或减少STW次数。