JVMNotes

第 23 章:JFR 与 JMC

zjc 于 2026-01-23 发布

这是《JVM 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 JFR(Java Flight Recorder)是 JVM 内置的事件记录系统,JMC(JDK Mission Control)是分析和可视化这些事件的客户端工具。相比采样 profiler 和业务日志,JFR 的开销低,适合在生产环境持续或按需采集。

JDK 11 以后 JFR 开源并成为 JDK 的重要诊断能力;JDK 8 中的可用性和参数存在商业授权与版本差异。

23.1 JFR 的价值

JFR 可以记录:

  1. CPU 热点方法;
  2. 内存分配位置;
  3. GC 与安全点;
  4. 锁竞争和线程等待;
  5. 类加载和异常;
  6. I/O 与socket相关事件;
  7. JVM 运行信息。
应用运行
   |
   +-- JFR Event Stream
         |
         +-- method sampling
         +-- allocation sampling
         +-- monitor blocked
         +-- GC / safepoint
         +-- JVM metadata

它解决的是“当时发生了什么”,而不是事后猜测。

23.2 启动方式

启动时开启持续记录:

java -XX:StartFlightRecording=filename=/log/app.jfr,maxsize=512m,maxage=1d \
  -jar app.jar

运行中开始:

jcmd <pid> JFR.start name=live filename=/log/live.jfr maxsize=512m maxage=1d

停止:

jcmd <pid> JFR.stop name=live

dump 当前内容:

jcmd <pid> JFR.dump name=live filename=/log/now.jfr

查看状态:

jcmd <pid> JFR.check

23.3 常用配置

配置 含义
filename 输出文件
maxsize 文件最大尺寸
maxage 事件保留时长
settings 事件模板,如 profile
dumponexit=true JVM 退出时输出
name 记录名称

低开销 profile 示例:

java -XX:StartFlightRecording=filename=/log/app.jfr,settings=profile,maxsize=1g,maxage=6h \
  -jar app.jar

settings=profile 通常包含更细的采样事件,但仍应根据实际压测确认开销。

23.4 JMC 界面

JDK Mission Control 打开 .jfr 文件后,常用页面包括:

页面 用途
Overview CPU、堆、线程、异常概览
Memory 堆、GC、分配热点
Code 方法热点和调用栈
Threads 线程状态和锁
Lock Instances 锁竞争
Exceptions 异常统计
I/O 文件和网络事件
Environment JVM 和系统信息

分析顺序建议:

1. 看记录时间窗口是否覆盖故障
2. 看 CPU 和堆是否异常
3. 找热点方法或热点分配
4. 查看线程状态和锁
5. 把事件时间与业务日志对齐

23.5 CPU 高分析

JMC 的 Code 页可以看到方法采样占比。

典型结论:

40% CPU -> com.demo.OrderParser.parse
          at OrderService.handle
          at HttpWorker.run

继续检查:

  1. 调用次数是否异常;
  2. 单次耗时是否变长;
  3. 是否存在正则回溯、大 JSON 解析、重复序列化;
  4. 是否 JIT 未热身;
  5. 是否锁竞争导致自旋;
  6. 是否容器 CPU limit 过低。

JFR 的方法采样是采样数据,适合定位热点方向;精确到每个调用的耗时应结合业务埋点或 Arthas trace。

23.6 内存分配热点

JFR 可以记录对象分配事件的采样栈。

典型现象:

byte[] 分配 800MB/s
  -> JsonWriter.toByteArray
  -> OrderController.export

排查方向:

  1. 是否一次性导出大文件;
  2. 是否循环中创建大集合;
  3. 是否每请求创建大缓冲;
  4. 是否反序列化对象过多;
  5. 是否日志拼接过度;
  6. 是否缓存未复用。

分配速率高会推高 Young GC 频率,在 ZGC 等收集器下还可能引发 Allocation Stall。

23.7 锁与线程分析

JFR 常见线程状态:

状态 含义
RUNNABLE 可运行或运行中
BLOCKED 等待进入 monitor
WAITING 无限期等待
TIMED_WAITING 限时等待
SLEEPING 睡眠

如果大量线程 BLOCKED:

1. 查看 Lock Instances
2. 找持有者线程
3. 查看持有时间
4. 定位方法调用栈
5. 缩小临界区或改并发结构

注意 synchronized 和 java.util.concurrent.locks 在事件呈现上可能不同,JDK 版本也会影响锁事件粒度。

23.8 常用事件

事件 用途
CPU Load 进程和机器 CPU
Java Monitor Blocked synchronized 竞争
Java Monitor Wait wait/notify 等待
Thread Park LockSupport.park
Allocation Sample 分配热点
Execution Sample CPU 热点
Garbage Collection GC 概览
Safepoint Begin/End 安全点
Exception Statistics 异常
Class Loading 类加载

可用 jfr metadata 查看当前 JFR 支持的事件类型:

jfr metadata

23.9 生产实践

建议策略:

  1. 常态低开销记录,保留最近数小时;
  2. 告警触发后立即 dump;
  3. 把 JFR 文件、GC 日志、业务日志时间对齐;
  4. 控制文件大小和保留周期;
  5. 限制下载权限,因为记录可能包含参数和对象信息;
  6. 变更后对比基线;
  7. 对高吞吐服务先压测 JFR 开销。

Kubernetes 场景要注意:Pod 重建后本地文件会丢失。可以挂载日志卷,或由运维平台在接收到告警后立刻采集上传。

本章小结

JFR 是 JVM 内置的低开销事件记录系统,配合 JMC 可以分析 CPU 热点、分配热点、锁竞争、GC 和线程状态。生产上适合常态保留最近事件,并在异常时快速 dump。分析时应把事件流与 GC 日志、业务指标和源码对齐,避免把采样热点直接等同根因。

思考题

  1. JFR 和堆 dump 分别适合什么问题?
  2. maxsize 和 maxage 为什么重要?
  3. 如何用 JFR 定位 Young GC 频繁的分配来源?
  4. 方法采样占比高是否一定代表根因?
  5. 如何在 Kubernetes 中设计 JFR 采集方案?