JVM垃圾收集器详解

发布时间:2026/10/5 10:16:41

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 告警本质上都是业务代码的大对象滥用、内存泄漏或长生命周期对象误晋升导致的。
延伸阅读

更多相关文章

2026/9/23 16:19:19

Vue 3 配置驱动式搜索组件封装:从 Schema 设计到高级功能实现

1. 项目缘起:为什么我们需要一个高度封装的搜索组件? 在后台管理系统、数据中台这类项目中,搜索功能几乎是每个列表页的标配。回想一下你最近参与的项目,是不是经常遇到这样的场景:产品经理拿着原型图过来,…

2026/10/5 17:18:00

插件加载失败排查指南:从报错到激活链路全拆解

我发现一个很有意思的现象:最近搜 “plugins” 这个词的人,大多数不是来学概念的,而是带着一行报错来的:“failed to load plugins”、“harness failed to load plugins”、“2 entries did not activate”,再往后看还…

2026/10/5 17:18:00

AngularJS与SQL Server深度整合:双向绑定、安全加固与性能优化

今年年中接手了一个老系统的升级改造,前端是 AngularJS,后端数据层是 SQL Server。很多朋友一听 AngularJS 就觉得是过时技术,我一开始也有点犹豫,但真正把业务跑通之后,我开始理解为什么那么多企业还在用这套组合。市…

2026/10/5 17:18:00

深入理解JavaScript构造函数、原型链与new的底层原理

在 JavaScript 生态里待得越久,你会越发觉一个事实:很多人写业务代码极其熟练,组件、状态管理、性能优化都能侃侃而谈,但一碰到“构造函数、原型、原型链”这三个词就支支吾吾。我自己也是这么过来的,早年在电商项目里…

2026/10/5 17:18:00

骶骨腰痛脊椎分割数据集实战:三切面、标签与避坑指南

简介:面向医学图像分割研究者和算法工程师的骶骨腰痛脊椎分割数据集,涵盖轴位面、冠状面、矢状面三个切面,共5个类别,并提供类别说明文件与可视化脚本。图像统一为512512尺寸,采用医学影像常用窗宽窗位增强处理&#x…

2026/10/5 17:18:00

JavaWeb学生成绩管理系统高分项目实战:从报错到交付

简介:本资源是一套完整、高分通过的JavaWeb学生成绩管理系统项目源码,专为计算机及相关专业学生设计,适用于课程设计、期末大作业与项目实战训练。系统涵盖学生、教师、课程、成绩等核心模块,采用JSPServletMySQL技术栈&#xff0…

2026/10/5 17:12:59

MFAC六大仿真案例:伪偏导数与动态线性化全解析

做控制方向的人应该都有过这种经历:定了MFAC(无模型自适应控制)的课题,翻开文献满眼都是伪偏导数、动态线性化、CFDL、PFDL这类概念,脑子里知道大概意思,但想真正跑一个能用的仿真程序,翻遍各种…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑