JVM 垃圾回收与性能调优实战

举报
yd_231768394 发表于 2026/10/25 21:38:19 2026/10/25
【摘要】 JVM 垃圾回收与性能调优实战本文深入剖析 JVM 内存结构与垃圾回收机制,帮助你理解 GC 原理并进行有效的性能调优。 JVM 内存结构┌─────────────────────────────────────────────────────────┐│ JVM ││ ┌─────...

JVM 垃圾回收与性能调优实战

本文深入剖析 JVM 内存结构与垃圾回收机制,帮助你理解 GC 原理并进行有效的性能调优。

JVM 内存结构

┌─────────────────────────────────────────────────────────┐
│                        JVM                              │
│  ┌─────────────────────────────────────────────────┐   │
│  │              线程共享区域                         │   │
│  │  ┌─────────────┐  ┌─────────────┐               │   │
│  │  │    堆 Heap  │  │ 方法区/元空间│               │   │
│  │  │             │  │ Metaspace   │               │   │
│  │  │ 新生代+老年代│  │  类元数据    │               │   │
│  │  └─────────────┘  └─────────────┘               │   │
│  └─────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────┐   │
│  │              线程私有区域                         │   │
│  │  虚拟机栈 │ 本地方法栈 │ 程序计数器               │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

堆内存分代(最核心):

┌─────────────────────────────────────────────────────────┐
│                        堆 Heap                          │
│  ┌───────────────────────────┐  ┌───────────────────┐  │
│  │        新生代 Young         │  │   老年代 Old/Tenured│  │
│  │ ┌──────┐ ┌────┐ ┌────┐    │  │                   │  │
│  │ │ Eden │ │ S0 │ │ S1 │    │  │  长期存活对象      │  │
│  │ └──────┘ └────┘ └────┘    │  │                   │  │
│  │  8       1      1  (比例)  │  │                   │  │
│  └───────────────────────────┘  └───────────────────┘  │
└─────────────────────────────────────────────────────────┘

对象的一生:从分配到回收

对象分配

Object obj = new Object();
// 1. 优先在 Eden 分配(TLAB:线程本地分配缓冲,减少锁竞争)
// 2. 大对象(超过 -XX:PretenureSizeThreshold)直接进老年代
// 3. 分配担保:Eden 不足时触发 Minor GC

可达性分析

GC Roots(根对象):
  - 虚拟机栈中引用的对象
  - 方法区静态属性引用的对象
  - 方法区常量引用的对象
  - 本地方法栈 JNI 引用的对象

从 GC Roots 出发做可达性分析:
  可达 → 存活
  不可达 → 可回收(还要经历"两次标记",给 finalize 机会)

对象晋升

新生代对象经历的 Minor GC 次数记录在对象头 Mark Word 中:

  年龄 = 经历 Minor GC 且存活 = 1
  ... 累计到达阈值(-XX:MaxTenuringThreshold,默认 15)

特殊:动态年龄判定
  Survivor 中同龄对象总和 > Survivor 空间一半
  → 大于等于该年龄的对象直接晋升老年代(避免 Survivor 溢出)

垃圾回收算法

┌─────────────────────────────────────────────────────────┐
│  1. 标记-清除(Mark-Sweep)                              │
│     ✓ 简单   ✗ 产生内存碎片                              │
│                                                         │
│  2. 标记-复制(Mark-Copy)                               │
│     ✓ 无碎片、效率高   ✗ 浪费一半空间(适合新生代)      │
│                                                         │
│  3. 标记-整理(Mark-Compact)                            │
│     ✓ 无碎片   ✗ 移动对象开销大(适合老年代)            │
│                                                         │
│  4. 分代收集:新生代用复制,老年代用整理/清除            │
└─────────────────────────────────────────────────────────┘

新生代回收(Minor GC / Young GC)流程:

Eden + S0 存活对象 ──复制──→ S1
  清零 Eden 和 S0,S0 与 S1 角色互换
  达到年龄阈值的对象晋升老年代

垃圾收集器演进

收集器 特点 分代 适用
Serial 单线程,Stop-The-World 新生代 客户端/小内存
ParNew Serial 多线程版 新生代 配合 CMS
Parallel Scavenge 关注吞吐量 新生代 后台计算
Serial Old 单线程,标记整理 老年代 客户端
Parallel Old 多线程,标记整理 老年代 吞吐优先
CMS 并发标记清除,低停顿 老年代 响应优先(已弃用)
G1 分 Region,可预测停顿 整堆 服务端默认
ZGC 染色指针,亚毫秒停顿 整堆 超大堆、低延迟
Shenandoah 并发压缩 整堆 低延迟

CMS 的四阶段

1. 初始标记(STW):标记 GC Roots 直接关联对象,快
2. 并发标记:与用户线程并行遍历,慢但不停顿
3. 重新标记(STW):修正并发期间的变动,快
4. 并发清除:与用户线程并行清理

问题:并发清除产生碎片(→ Full GC)、浮动垃圾、
     Concurrent Mode Failure

G1 的分 Region 设计

堆被划分为等大小 Region(1~32MB):

┌───┬───┬───┬───┬───┬───┬───┬───┐
│ E │ E │ S │ O │ O │ H │ E │ O │  E=Eden S=Survivor
└───┴───┴───┴───┴───┴───┴───┴───┘  O=Old   H=Humongous(大对象)

特点:
1. 逻辑分代,物理不分代(Region 动态扮演角色)
2. 优先回收"垃圾最多"的 Region(Garbage First)
3. 可设置目标停顿时间 -XX:MaxGCPauseMillis
4. 用 Remembered Set 记录跨 Region 引用,避免全堆扫描

GC 日志与调优

关键参数

# 打印 GC 日志(JDK 9+ 统一日志)
-Xlog:gc*,gc+heap=info:file=gc.log:time,uptime,level,tags

# 堆大小
-Xms4g -Xmx4g          # 生产建议初始=最大,避免动态扩缩

# 新生代
-Xmn1g                 # 新生代大小
-XX:SurvivorRatio=8    # Eden:S0:S1 = 8:1:1

# 元空间
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m

# 晋升阈值
-XX:MaxTenuringThreshold=15

# 选择收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200   # G1 目标停顿
-XX:+UseZGC

# OOM 时导出堆快照
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump.hprof

读取 GC 日志

[GC (Allocation Failure) [PSYoungGen: 65536K->8192K(76288K)]
 65536K->8200K(251392K), 0.0123456 secs]

关键信息:
- 触发原因:Allocation Failure
- 新生代:65536K -> 8192K(回收后)
- 整堆:65536K -> 8200K
- 耗时:12ms

关注:
1. 频率:Minor GC 太频繁 → 新生代过小或对象分配过快
2. 耗时:单次停顿过长 → 影响响应时间
3. 晋升:过早晋升 → 老年代压力大,检查 Survivor 与阈值
4. Full GC:频繁 Full GC 是危险信号

实战:常见问题排查

问题 1:频繁 Full GC

现象:应用响应变慢,CPU 高,日志频繁 Full GC

排查:
1. 是否内存泄漏?老年代回收后仍高位不降
   → jmap -histo:live <pid> 看对象直方图
   → dump 堆用 MAT/VisualVM 分析 GC Roots 引用链
2. 是否大对象直接进老年代?
   → 检查是否有超大数组/集合
3. 是否元空间/Metaspace 溢出?
   → 频繁动态生成类(CGLIB、反射)
4. Survivor 过小导致过早晋升?
   → 调大 -Xmn 或 SurvivorRatio

问题 2:内存泄漏定位

# 1. 查看堆概况
jmap -heap <pid>

# 2. 对象直方图(live 会触发 Full GC)
jmap -histo:live <pid> | head -30

# 3. 导出堆快照
jmap -dump:live,format=b,file=heap.hprof <pid>

# 4. 用 MAT 打开,找 Dominator Tree 和大对象的 GC Roots

问题 3:CPU 飙高定位

# 1. 找到高 CPU 线程
top -Hp <pid>
# 2. 线程 ID 转十六进制
printf '%x' <tid>
# 3. 导出线程栈并查找
jstack <pid> | grep -A 30 <hex_tid>

常用工具

工具 用途
jps 查看 Java 进程
jstat 实时 GC 统计 jstat -gcutil <pid> 1000
jmap 堆快照、对象直方图
jstack 线程栈(死锁、CPU 定位)
jinfo 查看/修改 JVM 参数
MAT / VisualVM / Arthas 堆分析与线上诊断

调优最佳实践

  1. 先测量,后调优:用数据说话,不要凭感觉改参数
  2. Xms = Xmx:避免堆动态伸缩带来的开销与抖动
  3. 优先选 G1:JDK 9+ 默认,通用性好;超大堆低延迟选 ZGC
  4. 设置合理停顿目标:如 -XX:MaxGCPauseMillis=200,但别设过小(会增加 GC 频率)
  5. 开启 HeapDumpOnOutOfMemoryError:OOM 现场可回溯
  6. 监控 GC 指标:停顿时间、频率、吞吐、晋升速率,接入监控告警
  7. 容器场景对齐内存:-Xmx 要与容器 limits 匹配,JDK 8u191+ 支持 -XX:+UseContainerSupport

总结

维度 要点
内存结构 堆分新生代(Eden+S0+S1)与老年代
回收判定 可达性分析,GC Roots 可达即存活
算法 新生代复制,老年代整理/清除
收集器 G1 通用,ZGC 低延迟超大堆
调优核心 减少 Full GC、控制停顿、避免泄漏

JVM 调优的本质:在吞吐量、停顿时间、内存占用三者间找平衡,一切以压测和监控数据为依据。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。