恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

JVM 垃圾回收调优实战:读懂 GC 日志,吃透 G1/ZGC 回收器与参数优化

  • 首页
  • 资讯中心
  • /
  • JVM 垃圾回收调优实战:读懂 GC 日志,吃透 G1/ZGC 回收器与参数优化

相关资讯

Vivado CORDIC IP核避坑指南:输入角度、输出位宽与AXI-Stream握手详解 2026/9/27 2:38:43
搞定WordPress查询表:用免费工具搞定域名服务器难题 2026/9/27 2:38:43
5步搞定发外链的论坛网站被黑挂马的对比评测与安全加固 2026/9/27 2:38:43

最新资讯

FPGA跨时钟域处理:从亚稳态到异步FIFO设计全解析
函数计算码包禁止跨区读:内网上传、跨区复制、同区更新
ESP32应用平台落地:用静态对象存储实现固件分发与OTA升级
Altium Designer装配图导出全攻略:智能PDF、打印输出与3D视图方法详解
PCIe 8GT/s物理层信号完整性:Preshoot与Boost原理及实战调试
消费品BI选型PoC设计:贴合核心业务场景的验证方案

今日推荐

从像素到笔画:srt-whiteboard-animation骨架笔迹追踪实现(Zhang-Suen细化+8邻接追踪)
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
新手入门看这篇:建设网站加盟避坑指南与SEO实操

本周热门

从像素到笔画:srt-whiteboard-animation骨架笔迹追踪实现(Zhang-Suen细化+8邻接追踪)
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
新手入门看这篇:建设网站加盟避坑指南与SEO实操

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

JVM 垃圾回收调优实战:读懂 GC 日志,吃透 G1/ZGC 回收器与参数优化

发布时间:2026/9/27 2:38:43
JVM 垃圾回收调优实战:读懂 GC 日志,吃透 G1/ZGC 回收器与参数优化 摘要GC 停顿与 OOM 是 Java 后端最常见的稳定性事故。本文用一段可运行 Demo 制造 Full GC 痛点带你看懂 JDK 9 统一日志框架下的 GC 日志各字段拆解 G1 与 ZGC 的回收原理与关键参数并给出一套「读日志 → 定位瓶颈 → 改参数 → 复测」的闭环调优 SOP结论可被你自己的运行结果证伪。一、背景与痛点为什么后端工程师必须懂 GC 调优GCGarbage Collection垃圾回收原本是为了让开发者从手动内存管理中解放出来但代价是回收时必须暂停应用线程STWStop-The-World。在后端服务里一次几十毫秒甚至数秒的 Full GC 停顿会直接表现为接口 RT 毛刺、TP99 飙升严重时触发上游超时、服务雪崩而 OOMOut Of Memory则会让进程直接被 kill。最常见的三类事故现象与可能根因如下现象可能的 GC 根因接口 TP99 偶尔飙到几百毫秒~几秒发生 Full GC 或长时间 Mixed GC大对象/晋升失败导致 Evacuation Failure进程突然退出日志出现java.lang.OutOfMemoryError堆真不够内存泄漏、Metaspace 类加载泄漏、GC overhead 被吃光吞吐频繁 Young GC 且停顿累加明显年轻代太小或短命对象分配速率过高老年代监控曲线锯齿且周期性 Full GC对象晋升过快、Humongous 大对象堆积或并发标记跟不上多数人的状态是只会设-Xmx和-Xms一旦出事就重启重启完问题还在。根因在于不懂「对象是怎么在分代之间流转的」也就看不懂日志、选不对回收器、调不动参数。下面这段 Demo 会主动制造 GC 压力——短命对象持续分配触发 Young GC同时不断堆积大对象撑大老年代/大对象区运行几轮后大概率能看到 Full GC 甚至 OOMimport java.util.ArrayList; import java.util.List; public class GcPressureDemo { // 大对象持有者超过单个 G1 Region 一半(默认约 1MB~16MB)就会被当作 Humongous 分配 static Listbyte[] humongousHolder new ArrayList(); static final int HUGE 1 * 1024 * 1024; // 1MB public static void main(String[] args) throws Exception { int iterations 200; for (int i 0; i iterations; i) { // 短命对象很快失去引用触发 Young GC byte[] shortLived new byte[512 * 1024]; // 大对象持续堆积撑大老年代 / 大对象区 humongousHolder.add(new byte[HUGE]); if (i % 50 0) { // 偶尔释放一部分制造「晋升-回收」的反复拉扯 humongousHolder.subList(0, Math.min(10, humongousHolder.size())).clear(); } Thread.sleep(5); } System.out.println(done, humongous count humongousHolder.size()); } }把这段程序用-Xmx512m跑起来再去翻 GC 日志你会看到本文后面要讲的那些字段和停顿类型。先记住我们调优的目标不是「消灭 GC」而是「让 GC 的停顿和频率落在业务可接受的范围内」。二、核心概念JVM 内存分代与 GC 算法基础JVM 堆内存采用分代模型依据是弱分代假说Weak Generational Hypothesis绝大多数对象朝生夕死。因此把堆分成两块用不同算法分别回收整体效率最高。Heap由 -Xmx 控制总大小 ┌────────────────────────────────────────────────────┐ │ Young Generation │ Old Generation │ │ ┌────────┬────────┬────────┐ │ (Tenured 老年代) │ │ │ Eden │ S0(From)│ S1(To) │ │ │ │ └────────┴────────┴────────┘ │ │ └────────────────────────────────────────────────────┘ Metaspace非堆受 -XX:MaxMetaspaceSize 限制存类元数据对象晋升路径新对象优先在 Eden 分配 → 一次 Minor GC 后存活对象被复制Copy到 SurvivorS0/S1 之间来回倒→ 每熬过一次 Minor GC 年龄 1达到年龄阈值默认MaxTenuringThreshold15后晋升老年代。三种基础回收算法各有取舍算法优点缺点适用区域标记-清除 Mark-Sweep不移动对象实现简单产生内存碎片较少单独使用复制 Copying无碎片、速度快浪费一半空间(From/To)年轻代(EdenSurvivor)标记-整理 Mark-Compact无碎片、空间利用率高移动对象成本高STW 更长老年代可达性分析与 STWGC 从 GC Roots栈帧局部变量、静态字段、JNI 引用等出发沿引用链标记存活对象不可达的即为垃圾。无论哪种回收器标记/清理/整理阶段都需要在某个时刻暂停应用线程STW区别只在于「暂停多久、多频繁」。所有现代回收器的演进本质上都是在想办法把工作搬出 STW、变成并发执行。三、GC 日志解读如何开启与逐字段读懂JDK 9 引入了统一 JVM 日志框架JEP 158用-Xlog取代了早期分散的-XX:PrintGCDetails等开关。注意-XX:PrintGCDetails已在 JDK 9 中废弃-verbose:gc等价于-Xlog:gc只输出基础信息。推荐的完整开启方式把日志落盘并做大小轮转java -Xmx512m -Xms512m \ -Xlog:gc*:filegc.log:time,uptime,level:filecount5,filesize20M \ -XX:UseG1GC \ GcPressureDemogc*tag 为 gc 及其子 tag 的全部日志time,uptime,level日志前缀装饰墙上时钟、进程启动后时长、日志级别filecount5,filesize20M保留 5 个文件、单个最大 20MB防止日志撑爆磁盘想要更细的诊断信息可加:debug变成-Xlog:gc*debug。下面是一条 G1 Young 回收日志已做精简和换行逐字段看[2024-01-15T10:22:31.1230800][info][gc,start ] GC(12) Pause Young (G1 Evacuation Pause) [info][gc,heap ] GC(12) Eden regions: 18-0(20) # Eden: 回收前18个Region, 回收后0, 总容量20 [info][gc,heap ] GC(12) Survivor regions: 2-3(4) # Survivor: 2→3, 容量上限4 [info][gc,heap ] GC(12) Old regions: 45-48 # 老年代: 有对象晋升, 45→48 [info][gc,heap ] GC(12) Humongous regions: 12-13 # 大对象区: 仍在增长 [info][gc,metaspace] GC(12) Metaspace: 12345K-12345K(12600K) [info][gc ] GC(12) Pause Young (G1 Evacuation Pause) 256M-198M(512M) 12.345ms最后一行是核心256M-198M(512M)表示回收前堆占用 256M、回收后 198M、堆总容量 512M12.345ms即本次 STW 停顿时长。当老年代Old regions持续上涨、Humongous regions不降就离 Full GC 不远了。关键 GC 原因cause与停顿类型对照日志中的类型/cause含义是否危险Pause Young (G1 Evacuation Pause)年轻代回收正常否Pause Young (Concurrent Start)并发标记启动的年轻代回收否Pause Full (G1 Compaction Pause)串行 Full GC会长时间 STW是Evacuation Failure复制存活对象时空间不足是常引发 Full GCAllocation Failure分配空间不足触发回收正常触发源System.gc()显式调用 System.gc()通常应避免四、G1 回收器原理与关键参数G1Garbage-First是自 JDK 9 起的默认服务端回收器设计目标是在保持高吞吐的同时提供可控、相对均匀的小停顿。它把堆切成若干等大的 Region默认大小由堆总量推导约 2048 个 Region逻辑上仍分代但不再有物理上连续的年轻代/老年代。它的回收生命周期是一条循环链路Young-only 阶段 并发标记阶段 Space-Reclamation (Evacuation Pause 回收年轻代) → (Concurrent Marking) → (Mixed GC 顺带回收 Old) ↑ │ └──────── 老年代占用达 IHOP 阈值时触发标记 ─────────┘Young-only只回收年轻代EdenSurvivor 复制并发标记当老年代占用达到 IHOP 阈值G1 启动并发标记一边跑应用一边标记存活对象Space-ReclamationMixed GC标记完成后在年轻代回收的同时顺带回收收益最高的若干老年代 Region。Humongous 大对象是 G1 的痛点超过单个 Region 一半大小的对象会被单独分配在连续 Humongous Region 中无法被高效整理堆积过多会直接触发 Full GC。G1 关键参数默认值以官方文档为准不同 JDK 小版本可能有微调参数默认值作用调优建议-XX:MaxGCPauseMillis200ms停顿时间目标设为业务可接受的 TP99G1 通过自适应调整年轻代大小去逼近而非硬卡-XX:InitiatingHeapOccupancyPercent45%触发并发标记的老年代占用阈值(IHOP)并发模式失败频繁时调低如 35让标记更早开始-XX:G1HeapRegionSize1~32MB目标约 2048 个 Region单个 Region 大小大对象多时适当调大减少 Humongous 数量-XX:G1ReservePercent10%预留空间防 to-space 溢出频繁 Evacuation Failure 时调大-XX:G1NewSizePercent/-XX:G1MaxNewSizePercent5% / 60%年轻代最小/最大占比一般不动禁止用-Xmn固定年轻代会破坏停顿自适应调优经验先用默认 设好-Xmx与停顿目标不要一上来就堆参数。绝大多数 G1 的 Full GC 来自 Evacuation Failure复制时没空间或大对象过多——这两类问题靠加堆、调 Region 大小、调 IHOP 解决而不是靠固定年轻代。五、ZGC原理与低延迟特性ZGC 是面向低延迟、大堆场景设计的回收器它把几乎所有昂贵工作标记、重定位、重映射都做成与应用线程并发执行因此停顿时间与堆大小基本无关官方表述为亚毫秒到数毫秒级支持的堆范围从 8MB 到 16TB。ZGC 能做到「不停应用也能回收」核心是两项机制对象引用 64 位: [保留 18 bit][元数据/颜色 4 bit][对象地址 42 bit] ↑ 彩色指针(Colored Pointers)把标记/重定位/成对等状态直接编码进指针本身 读屏障(Load Barrier)每次从堆中加载引用时插入屏障在访问前修正彩色指针指向的新地址因为状态在指针里、访问在屏障里修ZGC 不需要长 STW 去扫描/移动对象并发标记和并发重定位得以实现。开启方式JDK 21 推荐分代 ZGC将「年轻代」与「老年代」分开并发回收兼顾吞吐与延迟# JDK 21 分代 ZGC java -Xmx4g -Xlog:gc*:filegc-zgc.log \ -XX:UseZGC -XX:ZGenerational \ -XX:ConcGCThreads2 \ GcPressureDemo关键参数与取舍维度G1ZGC停顿来源Young/Mixed/Full与堆规模相关全部并发亚毫秒~数毫秒吞吐高默认平衡吞吐与停顿略低于 G1以部分吞吐换低延迟堆范围小~大8MB~16TB适用场景通用、响应时间优先强低延迟 SLA / 超大堆ZGC 最常用的调优项是-Xmx要给足「存活对象集合 分配速率」余量否则并发回收来不及和-XX:ConcGCThreads并发 GC 线程数越大回收越快但占 CPU 越多。另外 ZGC 默认会把空闲内存归还给操作系统若你的监控依赖「常驻内存」指标可用-XX:-ZUncommit关闭该行为。选型原则一句话默认 G1 求平衡当业务对停顿有硬性低延迟 SLA如 TP99 10ms或堆特别大时切 ZGC。六、常见调优套路与参数优化实战不要背参数表要建立一套可复现的调优闭环。通用 SOP① 基线默认 设 -Xmx开 -Xlog:gc* 跑业务/Demo记录吞吐与停顿 ↓ ② 定位瓶颈是吞吐不足还是停顿过长 / 频繁 Full GC ↓ ③ 改参数针对瓶颈改少数关键参数见下 ↓ ④ 复测用同一 Demo / 同一流量对比前后吞吐与 Max Pause ↓ ⑤ 断言调优是否真的生效可被自己的结果证伪吞吐不足适当放宽-XX:MaxGCPauseMillis或增大-Xmx检查是否误设-Xmn限制了年轻代自适应。停顿过长 / 频繁 Full GC看日志是否 Evacuation Failure 或大对象过多 → 增大-Xmx、用-XX:G1HeapRegionSize减少 Humongous、调低 IHOP 让并发标记更早启动。低延迟诉求切 ZGC-XX:UseZGC -XX:ZGenerational重点调-Xmx与-XX:ConcGCThreads。两组可直接对比的启动参数# G1 调优版给足堆、放宽停顿目标、提前并发标记 java -Xmx2g -XX:UseG1GC \ -XX:MaxGCPauseMillis150 \ -XX:InitiatingHeapOccupancyPercent35 \ -Xlog:gc*:filegc-g1.log \ GcPressureDemo # ZGC 版用低延迟回收器重点调堆与并发线程 java -Xmx2g -XX:UseZGC -XX:ZGenerational \ -XX:ConcGCThreads2 \ -Xlog:gc*:filegc-zgc.log \ GcPressureDemo用第一节的GcPressureDemo分别以上面两种配置运行典型对比数值随机器与 JDK 版本变化以你本地实测为准回收器配置吞吐(相对)最大停顿Full GC 次数默认 G1512m低高数百 ms~s多次G1 调优2g中约 150ms 目标0~1ZGC 分代2g中高 10ms0注意调大-Xmx能降低 Full GC 频次但不是越大越好——堆越大单次并发标记/整理的工作量也越大。结论必须靠你自己复测验证而非照抄。回收器选型对照按需选择CMS 自 JDK 9 起已废弃不要再使用回收器开启参数定位Serial-XX:UseSerialGC单核/极小堆Parallel-XX:UseParallelGC重吞吐、不敏感停顿G1-XX:UseG1GC响应时间优先默认ZGC-XX:UseZGC低延迟/超大堆七、问题排查思路从日志到根因真出问题时按下面清单逐层定位开全量日志-Xlog:gc*debug拿最完整信息用日志里的GC ID把「分配失败 → 回收 → 晋升 → 后续 Full GC」串成一条时间线。对照 OOM 类型Java heap space堆真的不够或存在内存泄漏对象该释放却一直被引用Metaspace类加载泄漏如频繁热部署、动态生成类未卸载GC overhead limit exceeded98% 时间在做 GC 却只回收了 2% 堆吞吐被吃光。判定停顿根因Evacuation Failure→ 复制失败加堆或降分配速率频繁Allocation Failure→ 年轻代太小 / 分配速率过高并发模式失败Concurrent Mode Failure 类→ 标记跟不上调低 IHOP 提前启动。排查决策树文字版出现 OOM / 长停顿 ├─ 日志里 Full GC 多且 Old 持续涨 ─→ 内存泄漏用堆转储(jmap/jcmd)看 TOP 对象 ├─ Humongous regions 高 ─→ 大对象过多调 G1HeapRegionSize 或改代码拆大对象 ├─ Evacuation Failure ─→ 加 -Xmx / 调 G1ReservePercent └─ 停顿仍超 SLA ─→ 切 ZGC(-XX:ZGenerational)辅助工具# 实时观察 GC 统计每 1 秒采样一次共 5 次 jstat -gc pid 1000 5 # 输出字段含义 # S0C/S1C Survivor0/1 容量(KB) # S0U/S1U Survivor0/1 已用(KB) # EC/EU Eden 容量 / 已用 # OC/OU Old 容量 / 已用 # MC/MU Metaspace 容量 / 已用 # CCSC/CCSU Compressed Class Space 容量 / 已用 # YGC/YGCT Young GC 次数 / 总耗时(秒) # FGC/FGCT Full GC 次数 / 总耗时(秒) # GCT GC 总耗时(秒) # 生成堆转储用于分析内存泄漏 jmap -dump:live,formatb,fileheap.hprof pid # 等价地也可用 jcmd 触发 jcmd pid GC.heap_dump heap.hprof常见误区清单不要这样做盲目把-Xmx调到机器内存上限结果单次 GC 工作量爆炸用-Xmn固定年轻代破坏 G1 的停顿自适应把System.gc()当救命稻草反而诱发不必要的 Full GC不看日志就抄网上的「最优参数」忽略了自己业务的分配特征。八、总结把 GC 调优变成可证伪的闭环一句话总结GC 调优 读日志定位瓶颈 懂回收器算法 针对性改少数关键参数 复测验证。它不是一个「背出神参数」的玄学而是一条可被自己运行结果证伪的工程闭环。关键结论速查主题要点G1 关键参数-Xmx、MaxGCPauseMillis(200ms)、IHOP(45%)、G1HeapRegionSize、G1ReservePercent(10%)ZGC 关键参数-Xmx、ConcGCThreads、-XX:ZGenerationalJDK21、-XX:-ZUncommit排查信号Evacuation Failure、Humongous 高、并发模式失败、OOM 类型选型默认 G1 平衡强低延迟 / 超大堆上 ZGC行动清单Checklist[ ] 收藏本文的-Xlog:gc*开启模板配到你自己服务里[ ] 跑通第一节GcPressureDemo亲眼看到 Full GC / 大对象日志[ ] 用第五节、第六节的两组参数复测建立你服务的 GC 基线吞吐 最大停顿[ ] 留一个可证伪的练习用同一 Demo 验证「调大-Xmx能降低 Full GC 频次」是否在你的环境下成立。GC 调优没有银弹但有方法。把日志读顺、把回收器原理搞懂下次再遇到 TP99 毛刺或 OOM你手里就有了一条从现象到根因的可执行路径。参考资料Oracle HotSpot G1 GC Tuning Guidehttps://docs.oracle.com/en/java/javase/21/gctuning/garbage-first-garbage-collector-tuning.htmlOracle HotSpot Z Garbage Collector Guidehttps://docs.oracle.com/en/java/javase/22/gctuning/z-garbage-collector.html© 2026 | 转载请注明出处 结论PASS

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号