底层原理

JVM 原理与调优深度解析

理解内存模型、垃圾回收与线上问题排查

高级 50 分钟 2026-05-15

涉及技术

Java 17 JVM Arthas G1/ZGC

文档简介

从 JVM 内存结构出发,深入讲解 G1/ZGC 垃圾回收器原理、常见 OOM 场景分析、Arthas 线上诊断工具使用,以及 GC 日志分析与 JVM 参数调优策略。

正文内容

JVM 内存结构

Java 虚拟机在执行 Java 程序时会把它管理的内存划分为若干个不同的数据区域。理解这些区域的用途和特性是 JVM 调优的基础。

┌─────────────────────────────────────────┐
│              堆(Heap)                    │
│  ┌─────────────┐    ┌─────────────────┐ │
│  │  年轻代      │    │     老年代       │ │
│  │  ┌─┐┌─┐┌─┐  │    │                 │ │
│  │  │E│S0│S1│  │    │                 │ │
│  │  └─┘└─┘└─┘  │    │                 │ │
│  └─────────────┘    └─────────────────┘ │
│              -Xms -Xmx                   │
├─────────────────────────────────────────┤
│  元空间(Metaspace)-XX:MaxMetaspaceSize  │
├─────────────────────────────────────────┤
│  虚拟机栈(VM Stack)-Xss                 │
│  本地方法栈(Native Stack)              │
│  程序计数器(PC Register)               │
├─────────────────────────────────────────┤
│  直接内存(Direct Memory)               │
│  -XX:MaxDirectMemorySize                 │
└─────────────────────────────────────────┘

垃圾回收器演进

从 Serial 到 ZGC,JVM 垃圾回收器经历了多次重大演进,核心矛盾始终是"吞吐量 vs 延迟"之间的权衡:

  • Serial GC:单线程,适合客户端应用
  • Parallel GC:多线程并行,追求高吞吐量
  • CMS:并发标记清除,降低停顿时间,但碎片化严重
  • G1:区域化收集,可预测停顿时间(Java 9 默认)
  • ZGC:低延迟(<10ms),可扩展至 TB 级堆(Java 15 生产可用)
  • Shenandoah:Red Hat 开发的低延迟 GC

G1 垃圾回收器详解

G1(Garbage First)将堆划分为多个大小相等的 Region,每个 Region 根据需要动态扮演 Eden、Survivor 或 Old 角色。

# G1 推荐参数(Java 17,8GB 堆)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200          # 目标最大停顿时间
-Xms8g -Xmx8g                      # 固定堆大小避免扩容
-XX:+AlwaysPreTouch               # 启动时预分配内存
-XX:InitiatingHeapOccupancyPercent=45  # 老年代占比触发并发标记
-XX:G1HeapRegionSize=16m          # Region 大小(根据堆大小自动计算)
-XX:+UseStringDeduplication        # 字符串去重(Java 8u20+)

ZGC:亚毫秒级停顿

ZGC 使用染色指针和读屏障技术,实现了全并发垃圾回收,停顿时间几乎不受堆大小影响:

# ZGC 启动参数(Java 17+)
-XX:+UseZGC
-Xms32g -Xmx32g
-XX:+ZGenerational                 # Java 21 分代 ZGC(性能更优)

# ZGC 日志分析
-Xlog:gc*:file=/var/log/gc.log:time,uptime,level,tags:filecount=5,filesize=100m

# 关键指标
[gc,start] GC(0) Garbage Collection (Proactive)
[gc,phases] GC(0) Pause Mark Start 0.012ms      # 停顿时间
[gc,phases] GC(0) Pause Mark End 0.015ms
[gc,phases] GC(0) Pause Relocate Start 0.018ms
[gc,phases] GC(0) Pause Relocate End 0.021ms
[gc,heap] GC(0) Heap: 8192M(8192M) -> 4096M(8192M)  # 回收前后堆占用

常见 OOM 场景诊断

OOM 是生产环境最常见的问题之一,不同区域的 OOM 有不同的排查思路:

// 1. Java heap space
// 原因:内存泄漏或堆设置过小
// 排查:
jmap -dump:format=b,file=heap.hprof <pid>
# 使用 MAT / VisualVM 分析 Dominator Tree

// 2. Metaspace
// 原因:动态生成类过多(如 CGLIB、反射)
// 排查:
-XX:+TraceClassLoading -XX:+TraceClassUnloading
# 观察类加载日志

// 3. Direct buffer memory
// 原因:NIO 直接内存泄漏(Netty 堆外内存未释放)
// 排查:
# 开启 Native Memory Tracking
-XX:NativeMemoryTracking=summary
jcmd <pid> VM.native_memory summary

// 4. Unable to create new native thread
// 原因:线程数超过系统限制(ulimit -u)
// 解决:
ulimit -u 65535  # 修改系统限制
# 或使用线程池替代无限创建线程

Arthas 线上诊断

Arthas 是阿里巴巴开源的 Java 诊断工具,可以实时查看 JVM 状态、方法耗时、线程堆栈等:

# 连接目标进程
java -jar arthas-boot.jar

# 热更新代码(无需重启)
redefine /path/to/UserService.class

# 方法执行监控(查看入参、返回值、耗时)
watch com.example.service.UserService getUserById \
  "{params, returnObj, throwExp}" \
  -x 2 \
  '#cost>100'    # 只监控耗时超过 100ms 的调用

# 火焰图生成
profiler start --event cpu
profiler stop --file /tmp/flamegraph.html

# 线程死锁检测
thread --block

# 反编译查看线上代码
jad com.example.service.UserService

JVM 调优实战建议

调优不是一蹴而就的,遵循"测量-分析-调整"的闭环:

  1. 先加 GC 日志和监控,收集运行数据
  2. 分析 GC 频率和停顿时间是否符合预期
  3. 通过 MAT 分析堆 dump,确认是否存在内存泄漏
  4. 调整参数后对比前后指标变化
  5. 避免盲目调优,每次只改一个参数