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. 缩小临界区或改并发结构

注意 synchronizedjava.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. maxsizemaxage 为什么重要?
  3. 如何用 JFR 定位 Young GC 频繁的分配来源?
  4. 方法采样占比高是否一定代表根因?
  5. 如何在 Kubernetes 中设计 JFR 采集方案?