压测时必须配置settings=profile,否则默认配置禁用或极低频启用jdk.executionsample事件,导致热点漏采;需配合足够duration、可写filename路径及合理interval(推荐10ms)才能稳定捕获真实瓶颈。

压测时JFR必须开settings=profile,否则基本看不到热点
默认的JFR配置(不指定settings)对CPU采样非常保守,jdk.ExecutionSample事件可能被禁用或设为极低频率(如100ms),压测中稍纵即逝的热点方法直接漏掉。只有settings=profile会强制启用高频方法采样、线程栈捕获和锁争用事件——这是压测场景下唯一能稳定捕获真实瓶颈的预设。
实操建议:
- 启动参数中必须显式写
settings=profile,不能省略 - 避免混用
settings=default或自定义空模板,它们默认关闭CPU采样事件 - OpenJDK 17+ 和 21+ 均支持该预设,无需额外JMC插件
duration要覆盖完整压测窗口,别只录前60秒
压测常有预热期、稳态期、峰值冲击期。如果只设duration=60s,很可能只录到预热阶段的“假平稳”,而错过峰值时的线程阻塞或GC风暴。JFR一旦停止就无法回溯,必须提前预估压测总时长并留出余量。
常见错误现象:
- 压测跑了5分钟,但JFR只录了开头60秒 → 看不到Full GC爆发点
- 用
jcmd <pid> JFR.start</pid>手动触发,却忘了在压测结束前JFR.stop→ 记录被截断或写入失败
推荐做法:对稳态压测,设duration=300s(5分钟);对含爬坡的长周期压测,改用maxsize=512MB + disk=true保底,防止OOM中断录制。
容器环境必须确认filename路径可写,否则JFR静默失效
K8s里JVM常以非root用户运行,而默认filename=recording.jfr会尝试写入当前工作目录(通常是只读的/app或/opt/app)。此时JFR看似启动成功,但实际没生成文件——连错误日志都不打,属于“静默失败”。
检查与修复方式:
- 启动前确保目标路径存在且有写权限:
mkdir -p /tmp/jfr && chmod 777 /tmp/jfr - 把
filename明确指向可写位置:filename=/tmp/jfr/cpu-$(date +%s).jfr - 加
--disk=true参数,强制落盘而非仅内存环形缓冲 - 压测后立刻检查文件是否生成:
ls -lh /tmp/jfr/,大小应≥10MB(低于1MB大概率采集失败)
高频采样不是越细越好,--interval=5ms在压测中反而失真
有人以为把采样间隔从默认10ms压到1ms就能“看清一切”,结果发现火焰图里全是Unsafe.park或Thread.sleep——因为过密采样把线程调度抖动、JVM safepoint等待都当成了“业务热点”。压测真正需要的是能区分“真计算”和“假等待”的信噪比。
实测经验:
- 10ms是平衡点:足够捕获>1ms的方法调用,又过滤掉微秒级调度噪声
- 5ms以下采样在高并发(>1k TPS)下会导致JFR自身占用1–2% CPU,干扰压测结果
- 若需更高精度,优先用
settings=profile+jfrprint --events jdk.ExecutionSample提取原始栈,再人工过滤park/sleep帧
最后提醒一句:JFR不是万能探针。它抓不到JNI内部耗时、无法穿透gRPC框架的序列化开销、对Reactive流中的异步回调栈还原也有限。压测中看到“CPU高但无热点”,先查是不是这些盲区在作祟。











