Java 11+ 默认禁用JFR,需显式添加-XX:+FlightRecorder启动参数;jcmd录制须指定name、duration和settings=profile以保障可追溯性与采样精度;分析应使用本地JMC而非浏览器,避免OOM;低开销不等于零开销,避免高频录制。

Java 11+ 默认没开 JFR,得手动解锁
JFR 在 Java 8u261+ 和 Java 11+ 是默认禁用的,不是“装了就能用”。不显式启用,jcmd 都看不到录制选项,更别说生成 .jfr 文件。
常见错误现象:jcmd <pid> VM.unlock_commercial_features 报错 “Unknown diagnostic command”,或者 jcmd <pid> JFR.start 直接失败 —— 其实是 JVM 启动时压根没带商业特性开关。
- Java 8u261+:必须加 JVM 参数
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder - Java 11+:去掉
UnlockCommercialFeatures,只留-XX:+FlightRecorder(该 flag 已默认启用,但某些发行版如 OpenJDK 构建可能仍需显式声明) - OpenJDK 17+(如 Temurin、Zulu)通常已内置支持,但生产环境仍建议显式加
-XX:+FlightRecorder,避免被构建脚本或容器镜像覆盖
jcmd 启动录制时别漏掉 name 和 duration
直接敲 jcmd <pid> JFR.start 看似能跑,但默认只录 60 秒、内存缓冲区极小(约 10MB),且没命名 —— 多次启动后你根本分不清哪个 .jfr 对应哪次排查。
使用场景:线上突发 CPU 尖刺、GC 频繁、HTTP 响应变慢,需要“现在立刻录 3 分钟看看”。
立即学习“Java免费学习笔记(深入)”;
- 务必指定
name=xxx,例如jcmd <pid> JFR.start name=cpu-spike-20240520-1430 - 设
duration=180s控制时长,避免无限录制拖垮老年代内存 - 加
settings=profile(而非默认的default)提升采样精度:方法调用栈、锁竞争、堆分配热点全都有 - 慎用
disk=true:默认写内存环形缓冲区(低开销),设为true才落盘;但若进程突然挂掉,未刷盘数据就丢了
分析 .jfr 文件时,别在浏览器里硬扛大文件
JFR 录制 5 分钟高负载应用,轻松产出 200MB+ 的 .jfr。用 Chrome 打开 JDK 自带的 JDK Mission Control(JMC)加载没问题,但直接拖进浏览器访问 https://localhost:7070(JFR over HTTP)大概率卡死或 OOM。
性能影响:JMC 后端解析本身吃 CPU 和堆内存,浏览器前端渲染大量事件(比如 10 万次 java.lang.ThreadParkEvent)会触发 JS GC 飙升,界面冻结。
- 优先用本地 JMC(
jmc命令或双击启动),它对大文件做了流式解析和懒加载 - 命令行快速筛信息:用
jfr print --events "jdk.CPULoad" --start "2024-05-20T14:30" my.jfr提取某类事件片段 - 导出为 CSV 再用 Excel 或 Grafana 分析:在 JMC 里右键图表 → Export → CSV,比截图或肉眼扫快得多
低开销 ≠ 零开销,高频录制反而伤性能
JFR 官方说“平均开销 stacktrace 采样、方法级别 Allocation 跟踪、或每毫秒采一次 CPU,开销会跳到 5~10%,尤其在大量短生命周期对象或高并发线程场景下。
容易踩的坑:有人为“保险起见”在所有 Pod 启动时就常驻开启 JFR,结果发现服务 RT 普遍升高、GC 次数变多 —— 其实是 JFR 缓冲区争用和事件采集干扰了 JIT 编译节奏。
- 生产环境推荐“按需开启”:只在怀疑问题时启动,录完立刻
JFR.stop name=xxx - 避免长期
settings=profile录制;日常可观测可用轻量default配置,仅开 GC、Thread、Socket 事件 - 检查是否重复启用了多个 recording:
jcmd <pid> JFR.check会列出所有活跃 recording,重复录制等于双倍开销
真正难的是判断“什么时候该开、开多久、开哪些事件”——这没法靠模板,得结合你的服务 SLA、GC 日志模式、以及上次排查时实际看到的瓶颈点来定。








