JFR / JMC 飞行记录器与高级诊断
JFR(JDK Flight Recorder)是随 JDK 内置的低开销性能事件录制器,配合 JMC(JDK Mission Control)可对生产环境做"黑匣子"式诊断。它的核心价值是极低开销 + 运行期全量事件,无需停服即可采集完整数据。
JFR 是什么
JFR 是 JVM 内置的事件记录系统,以极低开销(通常 <1%)持续记录运行数据:
| 特点 | 说明 |
|---|---|
| 低开销 | 采样与事件机制,对运行影响极小 |
| 无需停服 | 运行中启动/停止录制 |
| 全量数据 | 覆盖 GC、JIT、线程、锁、IO、堆等数百类事件 |
| 开箱即用 | JDK 11+ 自带,无需单独安装 |
与 jstat/jmap 等"快照式"工具不同,JFR 是连续的飞行记录——出事前启动的录制能保留事故前后的完整上下文。
JFR 事件模型
JFR 采集两类事件:
即时事件(Instant Event)
瞬时发生的事件,记录发生的时刻:
- GC 暂停开始/结束
- 线程阻塞
- 锁竞争
- 异常抛出
持续事件(Duration Event)
有时间跨度的采样事件,记录开始与结束:
- GC 各阶段耗时
- 方法执行时间(采样)
- 线程 CPU 时间
JFR 通过环形缓冲区存储事件,录制停止时落盘为 .jfr 文件,可在任意时刻回放分析。
JFR 使用:命令行录制
启动参数方式(开机录制)
bash
java -XX:StartFlightRecording=filename=rec.jfr,settings=profile,duration=60s \
-jar app.jar运行时动态录制
bash
jcmd <pid> JFR.start name=rec duration=60s filename=rec.jfr settings=profile
jcmd <pid> JFR.check # 查看录制状态
jcmd <pid> JFR.stop name=rec # 停止录制
jcmd <pid> JFR.dump name=rec # 导出已录制数据配置级别
| 配置 | 说明 |
|---|---|
default | 默认采样,开销更低 |
profile | 更高采样频率,采集更详细 |
JFR 事件类型速览
| 事件 | 关注点 |
|---|---|
| jdk.GCPhasePause | GC 停顿耗时 |
| jdk.ObjectAllocationInNewTLAB | 对象分配热点 |
| jdk.ThreadLockHeld / jdk.JavaMonitorEnter | 锁竞争 |
| jdk.Compilation | JIT 编译活动 |
| jdk.MethodSample | 方法采样(CPU 热点) |
| jdk.ExceptionThrown | 异常抛出统计 |
JMC:事件可视化分析
JMC(JDK Mission Control)是 JFR 的图形化分析工具:
bash
jmc # JDK 11+ 自带(JDK 8 需单独下载)
jmc rec.jfr # 直接打开录制文件核心视图
| 视图 | 用途 |
|---|---|
| 火焰图(Flame Graph) | 方法调用热点,自顶向下看 CPU 占比 |
| 垃圾回收 | GC 暂停时间线、各代占用 |
| 内存泄漏嫌疑(Leak Suspects) | 分配热点分析 |
| 线程(Threads) | 线程状态、锁等待、阻塞时间 |
| 代码缓存 | JIT 编译统计 |
| 事件浏览器 | 全量事件明细筛选 |
火焰图用法
火焰图按方法调用栈聚合 CPU 采样:
┌──────────────────────────────────┐
│ main() │ 顶部是调用者
│ └─ handleRequest() │
│ └─ parseJson() ███████ │ 色块越宽 = 耗时占比越高
│ └─ stringConvert() ███ │
└──────────────────────────────────┘- 色块宽度 = 采样命中占比
- 从下往上看:最宽路径即性能瓶颈所在
- 常见排查:解析、序列化、字符串操作、锁等待集中在同一调用栈
内存泄漏分析
JMC 的"内存泄漏嫌疑"基于对象分配采样:
1. 开启 profile 级录制,采集 5-10 分钟
2. JMC → 内存泄漏 → 查看嫌疑列表(按分配量排序)
3. 定位分配方法栈 → 回溯到业务代码与 MAT(事后堆转储)互补:JFR 看分配来源,MAT 看对象持有者。
实战案例
案例:接口响应偶发超时
现象:高峰期接口 P99 抖动,无法稳定复现
方案:
1. 常驻开启 JFR(default 级,开销 <1%)
2. 问题复现后 jcmd JFR.dump 导出
3. JMC 打开 → 垃圾回收视图:发现多次 GC 停顿超过 300ms
4. 火焰图 + 事件浏览器:定位到一次性大数据批量处理
结论:批量任务与高峰请求争抢内存,触发 Full GC案例:锁竞争导致吞吐下降
现象:TPS 上不去,CPU 不高但延迟高
方案:
1. JFR 录制 → 线程视图
2. 发现大量线程 WAITING/BLOCKED 在同一 Monitor
3. JavaMonitorEnter 事件明细:锁持有时间分布
结论:共享资源加锁过粗,改为分段锁常见问题
- JFR 影响性能吗? default 级通常 <1% 开销;profile 级更高但可短期使用。生产环境可常驻 default 级。
- JFR 与 jstat 的区别? jstat 是轮询快照,JFR 是持续事件记录;JFR 能覆盖事故前的时间窗口,jstat 只能看到采样瞬间。
- JDK 8 能用 JFR 吗? 需要商业授权(Oracle JDK 8);OpenJDK 8 与 JDK 11+ 开源可用。可用
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder开启(Oracle JDK 8)。