JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · JVM · GC · DIAGNOSTICS

JVM、GC 与诊断工具

JDK 21 · 速查主题 11 / 12 · 预计 30 分钟
进阶30 分钟JEP 439JEP 451
一句话JDK 21 的运行时主线是「更低停顿 + 更强诊断」:G1 仍是默认 GC,分代 ZGC 转正;JFR 与 jcmd 覆盖了从 GC、线程到虚拟线程的主要排障路径。先学会拿数据,再谈调优。

学完你要能自己回答

  1. G1 和 ZGC 的目标差异是什么?什么时候才应该选 ZGC?
  2. JDK 21 的 class 文件版本是多少?如何用代码读取运行时版本?
  3. 常见 OOM 类型分别意味着什么?第一动作应该做什么?

运行时与版本01

JDK 21 生成的 class 文件主版本号是 65(Java 8 是 52,每六个月 +1)。跨版本运行时,低版本 JVM 无法加载高版本 class。

Runtime.Version version = Runtime.version();
System.out.println(version);            // 21.0.12+8-LTS
System.out.println(version.feature());  // 21

System.out.println(System.getProperty("java.version"));
System.out.println(System.getProperty("java.vendor"));
System.out.println(System.getProperty("java.vm.name"));
System.out.println(System.getProperty("os.arch"));
# 查看 class 文件版本
javap -verbose MyClass.class | grep "major version"

# JDK 21 -> major version: 65
# JVM 参数与默认值
java -XX:+PrintFlagsFinal -version | grep -E "UseG1GC|UseZGC|MaxHeapSize"
从 JDK 10 起 JVM 能感知容器 CPU / 内存限制;容器里不要只依赖 -Xmx,也要关注 MaxRAMPercentage 与 ActiveProcessorCount。

内存结构:堆、元空间、直接内存02

区域存放常见参数溢出
堆(Heap)对象实例、数组-Xms / -XmxJava heap space
元空间(Metaspace)类元数据、方法字节码-XX:MaxMetaspaceSizeMetaspace
压缩类空间类指针压缩结构-XX:CompressedClassSpaceSizeCompressed class space
直接内存NIO DirectByteBuffer-XX:MaxDirectMemorySizeDirect buffer memory
线程栈每个平台线程的栈-Xssunable to create native thread
代码缓存JIT 生成的机器码-XX:ReservedCodeCacheSizeCodeCache is full

JDK 21 的堆仍然分代(年轻代 / 老年代),G1 按 Region 管理。对象优先在 Eden 分配,经历多次 GC 后晋升老年代;大对象可能直接进入 Humongous Region。

# 查看堆与 GC 概况
jcmd <pid> GC.heap_info
jcmd <pid> GC.class_histogram | head -30
jstat -gcutil <pid> 1000 10

# OOM 时自动 dump
java -Xmx512m -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/tmp/heap.hprof -jar app.jar
jmap -histo:live 会触发一次 Full GC,生产环境慎用;优先用 jcmd GC.class_histogram 或 JFR 的堆采样。

GC 选型:G1 默认,ZGC 低延迟03

JDK 21 默认 G1。ZGC 在 15 转正,21 引入分代 ZGC(JEP 439),需要在 21 上显式开启分代模式;到 23 分代成为默认,24 移除非分代模式。

GC目标停顿适用
Serial小堆、单核较长客户端、小工具
Parallel吞吐优先较长批处理、离线计算
G1平衡吞吐与停顿可预测(默认目标 200ms)大多数服务端默认
ZGC极低停顿通常 < 1ms(与堆大小弱相关)大堆、延迟敏感
Shenandoah低停顿较低低延迟,另一条实现路线
# G1(JDK 21 默认)
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar

# ZGC 分代模式(JDK 21 需显式开启)
java -XX:+UseZGC -XX:+ZGenerational -Xmx16g -jar app.jar

# 查看 GC 日志
java -Xlog:gc*:file=gc.log:time,uptime,level,tags -jar app.jar
  • 堆越大,ZGC 的相对优势越明显;小堆用 G1 通常足够。
  • ZGC 的吞吐与内存开销通常高于 G1,不要只看停顿。
  • MaxGCPauseMillis 是目标不是保证;G1 会通过调整年轻代大小逼近目标。
  • 不要同时开启多个 GC 参数;-XX:+UseZGC 与 -XX:+UseG1GC 互斥。
  • 升级到 JDK 23+ 后,ZGC 分代是默认,不需要再写 ZGenerational。
调优顺序:先确认瓶颈是 GC(日志/停顿),再调堆与 GC;多数「GC 问题」其实是内存泄漏或分配速率过高,换 GC 只是掩盖症状。

jcmd / JFR / 线程转储04

JDK 21 的诊断入口首选 jcmd:它把 JFR、线程、GC、VM 信息整合到一个工具里,不需要额外安装。JFR 是低开销的持续记录器,适合生产环境。

命令用途
jcmd <pid> VM.version版本与 JVM 信息
jcmd <pid> VM.flags当前生效参数
jcmd <pid> GC.heap_info堆使用与 GC 概况
jcmd <pid> Thread.print线程转储(平台线程)
jcmd <pid> Thread.dump_to_file -format=jsonJSON 线程转储(含虚拟线程)
jcmd <pid> JFR.start duration=60s filename=app.jfr启动 JFR 记录
jcmd <pid> JFR.dump filename=app.jfr导出运行中的 JFR
jfr summary app.jfr查看记录概况
jfr print --events jdk.GCPhasePause app.jfr打印指定事件
# 发现 Java 进程
jps -l

# 采集 60 秒 JFR(低开销)
jcmd <pid> JFR.start duration=60s filename=/tmp/app.jfr

# 线程转储
jcmd <pid> Thread.print > /tmp/threads.txt

# 查看死锁
jcmd <pid> Thread.print -l | grep -A 20 "Found one Java-level deadlock"

JFR 的默认配置开销很低,适合长期开启。线上排障时先看 jdk.GCPhasePause、jdk.ExecutionSample、jdk.VirtualThreadPinned、jdk.JavaMonitorEnter 等事件。

OOM 类型与第一动作05

错误含义第一动作
Java heap space堆内存不足dump 堆,查大对象 / 泄漏;再考虑调大 -Xmx
Metaspace类元数据耗尽查类加载器泄漏、动态生成类;限制 MaxMetaspaceSize
Direct buffer memory直接内存耗尽查 Netty / NIO 泄漏;设置 MaxDirectMemorySize
unable to create native thread平台线程创建失败降低线程数 / 栈大小;考虑虚拟线程
GC overhead limit exceededGC 占用过高且回收很少几乎总是堆太小或泄漏,先 dump
Requested array size exceeds VM limit数组长度超限检查业务计算是否溢出
Compressed class space压缩类空间耗尽查类加载泄漏,调整空间参数
# 生产环境建议常开
java -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/data/dumps \
     -Xlog:gc*:file=/data/logs/gc.log:time,uptime:filecount=5,filesize=50M \
     -jar app.jar
  • 先保现场:堆 dump、GC 日志、线程转储,再重启。
  • 不要在没有 dump 的情况下反复重启;问题会重复出现。
  • Metaspace OOM 常见于热部署、动态代理、脚本引擎反复生成类。
  • Direct buffer memory 不占堆,堆 dump 看不出来;需要 NIO 泄漏检测或 JFR。

JIT、启动与性能06

HotSpot 使用分层编译:C1 快速编译,C2 深度优化。方法需要「热」到阈值才会被 C2 编译,因此短命进程、微基准测试很容易测到解释执行或 C1 代码。

  • 性能测试必须预热,或使用 JMH 这类框架;不要用 System.currentTimeMillis 循环测单次调用。
  • 逃逸分析可以把未逃逸对象标量替换,减少分配;但这依赖 JIT,不要据此写「聪明」代码。
  • -Xint 只用于调试,不要用于生产。
  • 代码缓存满会导致退化为解释执行,日志里会出现 CodeCache 相关警告。
  • 启动优化可考虑 AppCDS(JDK 10+)、AOT 缓存(JDK 24+ 逐步成熟)。
# 查看 JIT 与代码缓存
jcmd <pid> Compiler.codecache
jcmd <pid> Compiler.queue

# 统一日志查看类加载 / GC
java -Xlog:class+load=info,gc*=info -jar app.jar
性能问题先分清是 CPU、内存、GC、锁还是 I/O。JFR 的 ExecutionSample 看 CPU 热点,JavaMonitorEnter 看锁竞争,GCPhasePause 看停顿;不要一上来就调 JVM 参数。

虚拟线程的运行时观测07

虚拟线程不是 OS 线程,传统 jstack 只能看到载体线程。JDK 21 提供了 JSON 线程转储与 JFR 事件,专门用于观察虚拟线程。

# JSON 转储,包含虚拟线程
jcmd <pid> Thread.dump_to_file -format=json /tmp/threads.json

# JDK 21 检测 pinning
java -Djdk.tracePinnedThreads=full -jar app.jar

# JFR 事件
jcmd <pid> JFR.start duration=60s filename=/tmp/vt.jfr
jfr print --events jdk.VirtualThreadStart,jdk.VirtualThreadEnd,jdk.VirtualThreadPinned /tmp/vt.jfr
  • jdk.VirtualThreadPinned 默认记录超过 20ms 的钉住事件。
  • 虚拟线程数量不是问题本身;要关注载体线程利用率与下游瓶颈。
  • JDK 24+ 已消除 synchronized 导致的 pinning,升级后相关告警会消失。
  • JFR 的 jdk.ThreadStart 与 jdk.VirtualThreadStart 是不同事件,不要混淆。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
分代 ZGC21 正式(需开启)JEP 439;JDK 23 成为默认,24 移除非分代模式。
默认 GCG1JDK 9 起 G1 为默认;JDK 27 起所有环境默认 G1。
紧凑对象头24 实验 / 25 正式JDK 21 不可用。
JFR 开源11 正式JDK 8 需商业许可,11 起开源。
容器感知10 起JDK 8u191+ 也有部分支持。
动态加载 agent 警告21 警告JEP 451。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. G1 和 ZGC 的核心目标差异是什么?
G1 追求吞吐与可预测停顿的平衡,通过 Region 和停顿目标调优,适合大多数服务;ZGC 追求极低停顿(通常亚毫秒级),通过并发标记/转移把停顿与堆大小解耦,适合大堆、延迟敏感场景,但通常有更高的内存与吞吐开销。
02. JDK 21 的 class 文件版本是多少?
主版本号 65。Java 8 是 52,之后每个 feature release 加 1,因此 21 对应 65,25 对应 69。
03. 遇到 Metaspace OOM 应该先查什么?
先查类加载器泄漏与动态类生成:热部署、动态代理、脚本引擎、字节码框架都可能不断生成类。用类直方图/类加载日志定位,设置 MaxMetaspaceSize 只是兜底,不是根治。
04. 为什么虚拟线程不能用 jstack 看到?
jstack 展示的是 OS 线程(载体线程),虚拟线程由 JVM 调度、栈存储在堆上,不是 OS 线程。JDK 21 要用 jcmd Thread.dump_to_file -format=json 或 JFR 事件观察虚拟线程。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。