十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JVM垃圾收集器详解

JVM垃圾收集器详解 文章目录 1. JVM垃圾收集器线上事故触发的再思考 2. JVM收集器类别分代 vs 分区架构演进 3. Serial 串行收集器单线程的特定场景价值⚡ 4. Parallel并行收集器吞吐量优先的利器与代价 5. CMS与G1并发收集器低延迟演化与并发标记坑点 5.1 CMSConcurrent Mark Sweep碎片化与Concurrent Mode Failure 5.2 G1Garbage-FirstRegion化与停顿预测模型并发标记核心SATBSnapshot-At-The-Beginning与写屏障⚡ 6. ZGC并发收集器10ms到1ms的着色指针革命 7. JVM垃圾收集器必考点与调优指南总结 1. JVM垃圾收集器线上事故触发的再思考去年双十一预热期间我们组一台负责核心风控过滤的容器节点突然告警接口 RT响应时间从 8ms 飙升至 12,000ms网关层大量请求超时返回 504。排查日志发现内存直接触发了频繁的java.lang.OutOfMemoryError: GC overhead limit exceeded2025-11-10T14:22:01.3020800:[GC(Allocation Failure)[PSYoungGen: 3145728K-102400K(3145728K)]10485760K-10485600K(10485760K),4.2839201secs]2025-11-10T14:22:05.5870800:[Full GC(Ergonomics)[PSYoungGen: 102400K-102400K(3145728K)][ParOldGen: 7340032K-7340030K(7340032K)]10485600K-10485430K(10485760K),[Metaspace: 124010K-124010K(1056768K)],12.1839201secs]java.lang.OutOfMemoryError: GC overhead limit exceeded at java.util.Arrays.copyOf(Arrays.java:3212)at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:123)原因很硬核JVM 花了 98% 以上的时间做 GC但只回收了不到 2% 的内存。应用程序线程在长时间 STWStop-The-World中处于假死状态。垃圾收集器Garbage Collector, GC并不是简单的“自动打扫卫生”。在高并发、大内存场景下GC 算法的选择直接决定了系统的可用性指标SLA。 2. JVM收集器类别分代 vs 分区架构演进从 JDK 1.3 到 OpenJDK 21JVM 垃圾收集器的演进路线可以划分为两个重要维度物理分代与逻辑分区/无分代。--------------------------------------------------------------------------------- | JDK 物理分代时代 | | 年轻代 (YoungGen: Eden S0 S1) | 老年代 (OldGen) | | [Serial] [ParNew] [Parallel Scavenger] | [Serial Old] [CMS] [Parallel Old] | --------------------------------------------------------------------------------- | v --------------------------------------------------------------------------------- | JDK 逻辑分区 / 无分代时代 | | [G1 GC] : 划分为 2048 个等大 Region逻辑保留 Eden/Survivor/Tenured | | [ZGC] : 动态 Region (2MB/32MB/2MB*N)Colored Pointers Load Barriers | | [Shenandoah] : 读屏障/Brooks Pointers全阶段并发 GC | ---------------------------------------------------------------------------------下面是各大主力收集器的核心特性对比收集器线程模型核心目标适用堆内存范围JDK 版本状态吞吐量/延迟侧重Serial / Serial Old单线程极小资源消耗 512 MB 512\text{MB}512MB仍保留 (客户端模式)高吞吐 (仅单核)Parallel Scavenger/Old多线程并行最大化 CPU 利用率1 GB − 8 GB 1\text{GB} - 8\text{GB}1GB−8GBJDK 8 默认吞吐量优先CMS多线程并发降低停顿时间2 GB − 16 GB 2\text{GB} - 16\text{GB}2GB−16GBJDK 9 废弃, JDK 14 移除延迟优先G1 GC多线程并发可预测的停顿目标4 GB − 64 GB 4\text{GB} - 64\text{GB}4GB−64GBJDK 9 默认延迟/吞吐平衡ZGC多线程全并发 1 ms 1\text{ms}1ms极低停顿16 MB − 16 TB 16\text{MB} - 16\text{TB}16MB−16TBJDK 15 正式商用极低延迟优先 3. Serial 串行收集器单线程的特定场景价值Serial年轻代复制算法和 Serial Old老年代标记-整理算法是最古老的收集器。Client App: ------ [ User Thread Running ] ------ [ STW ] ------- [ User Thread Running ] | GC Thread: [ Single GC Thread ]Warning (生产坑点)绝对不要在生产环境的 4C8G 以上容器中指定-XX:UseSerialGC。单线程 GC 清理 8GB 堆内存时会导致数分钟的 STW。但 Serial 并没有死。在 Docker 容器化时代单核 CPU或 0.5 核 Sidecar 容器、内存限制在 250MB 左右的小型微服务或 Lambda 边缘函数中-XX:UseSerialGC依然是最佳选择因为它没有任何多线程上下文切换nvcswch的额外消耗垃圾回收器占用的额外内存Footprint极小。⚡ 4. Parallel并行收集器吞吐量优先的利器与代价Parallel Scavenger / Parallel Old 是 JDK 8 的默认组合。其核心逻辑是充分利用多核 CPU 的计算资源在 GC 时启动多个垃圾回收线程并行清理以缩短 STW 时间达到最大吞吐量Throughput。我们可以通过模拟触发 Parallel GC 的配置来观测其表现// VM Options: -XX:UseParallelGC -Xms512m -Xmx512m -XX:PrintGCDetailspublicclassParallelGCTest{publicstaticvoidmain(String[]args){Listbyte[]allocationListnewArrayList();for(inti0;i1000;i){// 每次分配 1MB 内存allocationList.add(newbyte[1024*1024]);try{Thread.sleep(10);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}}}控制台 GC 日志如下[GC(Allocation Failure)[PSYoungGen: 131072K-21504K(153088K)]131072K-103432K(502272K),0.0182410secs][Times:user0.06sys0.01,real0.02secs][Full GC(Ergonomics)[PSYoungGen: 21504K-0K(153088K)][ParOldGen: 81928K-102400K(349184K)]103432K-102400K(502272K),[Metaspace: 3120K-3120K(1056768K)],0.0892301secs][Times:user0.32sys0.00,real0.09secs]核心调优参数折中Trade-off-XX:GCTimeRatio99意味着允许垃圾回收时间占总运行时间的1 / ( 1 99 ) 1 % 1/(199) 1\%1/(199)1%。-XX:MaxGCPauseMillis200给 Parallel 设置停顿时间上限。但该参数存在反作用——为了满足停顿时间上限JVM 会自动缩小年轻代堆大小导致更频繁地触发 GC吞吐量反而大幅下降。 5. CMS与G1并发收集器低延迟演化与并发标记坑点 5.1 CMSConcurrent Mark Sweep碎片化与Concurrent Mode FailureCMS 是第一个实现了用户线程与垃圾收集线程并发执行的收集器针对老年代设计。1. 初始标记 (STW) -- 仅标记 GC Roots 直接相连的对象 (极快) 2. 并发标记 -- 开启并发标记线程沿 GC Roots 追踪对象图 (耗时长不 STW) 3. 重新标记 (STW) -- 修正并发标记期间因用户线程继续运行而导致标记变动的对象 (增量更新) 4. 并发清除 -- 清除已死对象 (与用户线程并发)Warning (线上踩坑)CMS 使用标记-清除Mark-Sweep算法必定产生大量的内存碎片。当老年代无法找到足够大的连续空间分配大对象时会触发严重的Concurrent Mode Failure强制退化为单线程 Serial Old 整理模式造成长达数秒甚至几十秒的 STW 灾难。解决方法是强制开启内存碎片整理-XX:UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction70# 老年代达到 70% 即触发 CMS保留空间应对并发浮动垃圾-XX:UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction2# 2 次 Full GC 后做一次压缩整理 5.2 G1Garbage-FirstRegion化与停顿预测模型G1 彻底打破了传统的物理分代界限。它把整个 Java 堆划分为约 2048 个大小相等的独立Region1 MB − 32 MB 1\text{MB} - 32\text{MB}1MB−32MB必须是 2 的幂。------------ | E | S | O | E | E: Eden Region ------------ S: Survivor Region | H | O | E | O | O: Old Region ------------ H: Humongous Region (大对象空间, 0.5 Region) | O | S | H | E | ------------G1 的核心设计是可预测的停顿模型Pause Prediction Model。它使用衰减均值Decay Average算法实时统计每个 Region 的回收价值回收可释放的内存 vs 回收所消耗的时间优先回收价值最大的 RegionGarbage-First 的由来。并发标记核心SATBSnapshot-At-The-Beginning与写屏障在并发标记阶段为了解决用户线程与 GC 线程并发执行时漏标原本存活的对象被误删的问题G1 采用了SATB技术。// 逻辑上的写屏障伪代码模拟voidpre_write_barrier(oop*field_address){if(G1State::is_concurrent_mark_in_progress()){oop old_value*field_address;if(old_value!NULL){// 将旧引用压入 SATB 本地缓冲区保障并发标记开始时的对象图快照不被破坏enqueue_satb_buffer(old_value);}}}推荐的线上 G1 参数配置-XX:UseG1GC-XX:MaxGCPauseMillis200# 目标停顿时间默认 200ms-XX:InitiatingHeapOccupancyPercent45# 堆使用率达到 45% 时触发并发标记周期 (IHOP)-XX:G1HeapRegionSize16m# 显式指定 Region 大小-XX:G1ReservePercent10# 预留 10% 空间作为 Eden 到 Survivor 的缓冲区防止 Evacuation Failure⚡ 6. ZGC并发收集器10ms到1ms的着色指针革命从 OpenJDK 15 商用的ZGCZ Garbage Collector是真正意义上的“低延迟怪物”。在 16TB 的超大堆下能将 STW 停顿时间控制在1ms 以内。ZGC 实现了全阶段并发并发标记、并发预备重分配、并发重分配、并发重映射其核心黑科技在于着色指针Colored Pointers与读屏障Load Barriers。在 64 位机器上ZGC 将寻址指针的高几位借用来存放 GC 状态标记--------------------------------------------------------------- | 18 bits (Unused) | 4 bits (GC Metadata| 42 bits (Object Address| | | Marked0/Marked1/ | 支持 4TB/16TB 物理内存)| | | Remapped) | | ---------------------------------------------------------------// ZGC 读屏障的核心执行逻辑JIT 编译时植入oopzgc_load_barrier(oop*reference_address){oop bad_ref*reference_address;// 检查指针的 GC Metadata 标志位如果不是 Bad Color 则直接返回极快if(is_bad_color(bad_ref)){// 触发 slow_path如果对象正在被移动更新指针并返回新地址 (自愈指针)returnresolve_bad_color(reference_address);}returnbad_ref;}# JDK 17 / JDK 21 生产推荐配置java-XX:UseZGC-XX:ZGenerational-Xms32g-Xmx32g-XX:ReservedCodeCacheSize512m-jarapp.jar在 JDK 21 中ZGC 已经默认引入了分代 ZGCGenerational ZGC解决了旧版 ZGC 在高分配速率Allocation Rate下容易导致 Allocation Stall分配停顿的隐患。 7. JVM垃圾收集器必考点与调优指南总结在面试与线上故障排查中只需牢记以下硬核逻辑链条垃圾回收三要素STW 停顿时间、内存吞吐量、堆空间 Footprint。三者不可兼得调优本质上是对业务场景的Trade-off。三色标记与漏标解决黑色已标记且子节点已扫描、灰色自身已标记但子节点未扫描完、白色未标记。CMS 解决漏标增量更新Incremental Update破坏“黑色指向白色”条件通过写屏障记录新引用重新标记阶段 STW 扫描。G1 / ZGC 解决漏标原始快照SATB破坏“灰色断开白色”条件通过写屏障记录断开的引用将旧对象视作存活。ZGC 的核心突破利用着色指针 读屏障实现了对象的并发移动Relocation从而把转移阶段的 STW 降低到了近乎常数级别 1 ms 1\text{ms}1ms。生产选型极简规整 堆内存 4G Parallel GC (吞吐量优先) 或 G1 GC 堆内存 4G-64G G1 GC (平衡延迟与吞吐) 堆内存 64G / 低延迟 SLA 敏感情境 分代 ZGC (JDK 21)GC 调优不是调整几个-XX参数就能解决的灵丹妙药绝大多数 Full GC 告警本质上都是业务代码的大对象滥用、内存泄漏或长生命周期对象误晋升导致的。
返回列表