本节摘要:四件套是"到场快照",JFR 是"事后可回放的行车记录仪":以极低开销持续记录事件,事故发生后回放当时数据;Async-Profiler 则提供方法级 CPU 与分配的火焰图。本节给出两者的最小上手会话,并把它们接进持续监控体系的链路里。
6.1 的体检有个先天短板:它只反映"现在"。线上常见的是"事故发生在凌晨两点,早晨上班才知道"——现场早已消失。连续观测设备就是为这类场景准备的:JFR(Java Flight Recorder)常驻低开销记录,事后取出回放;Async-Profiler 产出方法级火焰图,回答"时间与内存花在哪"。两者的开销控制是它们能常驻生产的原因——官方口径下 JFR 对吞吐的影响约在百分之一量级。
第一步,启动记录。JDK 11 起 jcmd 直接带 JFR 能力,不需要额外商业授权(这个变化意义重大,此前 JFR 属于商业特性):
# 挂一个最多保留两小时、滚动的持续记录 $ jcmd 29104 JFR.start name=continuous settings=profile maxage=2h maxsize=256m 29104: Started recording 1. No limit (duration/size), use maxage/maxsize. # 拷出当前数据(不用先停) $ jcmd 29104 JFR.dump name=continuous filename=incident.jfr $ jcmd 29104 JFR.stop name=continuous
settings=profile 是"体检套餐"选择:default 开销更低事件更少,profile 更全(方法采样、锁、分配全开),事故排查用 profile。maxage 与 maxsize 让记录滚动,保证常驻不撑爆磁盘。
第二步,回放。jfr 工具(JDK 自带)直接读文件:
$ jfr summary incident.jfr | head -n 14 Version: 2.0 Chunk Count: 1 Event Count: 84213 Event Type Count Size (bytes) jdk.ExecutionSample 18402 736080 jdk.JavaMonitorEnter 3120 124800 jdk.ObjectAllocationSample 12840 308160 jdk.GarbageCollection 12 1224 jdk.GCPhasePause 12 1176
summary 先看"有什么事件、各多少",再按需取明细。事故回放最常用的几类:ExecutionSample 看当时 CPU 在执行什么(方法采样);JavaMonitorEnter 看锁等待时长;ObjectAllocationSample 看谁在大量分配(泄漏与分配风暴的凶手搜索);GCPhasePause 汇总当时停顿。逐条打印样例:
$ jfr print --events jdk.ObjectAllocationSample incident.jfr | head -n 18 jdk.ObjectAllocationSample { startTime = 10:22:41.812 objectClass = byte[] (class byte[]) weight = 4.2 MB stackTrace = [ ... com.demo.export.ExcelWriter.writeRow(ExcelWriter.java:121) com.demo.export.ReportJob.run(ReportJob.java:58) ] }
第三步,读事件时间轴。JFR 的价值在"时间对齐":把分配事件、GC 停顿、锁等待按同一时间轴摆开,因果一目了然——比如"报表任务启动后三分钟,分配采样里 ExcelWriter 占比飙升,随后 GC 频率翻倍",一条分配风暴的证据链就闭合了。可视化回放用 JMC(Java Mission Control)打开同一文件更直观,这里不展开工具教学,事件模型才是根。
JFR 事件模型通用但读起来繁琐;想一眼看出"哪里最热",火焰图更直接。Async-Profiler 通过 attach 方式挂在目标进程上,同时支持 CPU、分配、锁竞争多种采样:
# 采 60 秒 CPU 火焰图 $ ./profiler.sh -d 60 -f cpu.html 29104 # 采分配火焰图(按分配字节) $ ./profiler.sh -d 60 -e alloc -f alloc.html 29104
打开生成的 HTML,横轴是调用栈(越宽占比越大),纵轴是栈深,父在上子在下。读图两步走:先找最宽的"平顶"——平顶意味着大量时间直接耗在这个方法上;再沿塔尖向上确认调用来源。CPU 图上的宽平顶是热点代码,分配图上的宽平顶是内存大户(6.3 的 CPU 病案就用它收尾)。它比 JFR 采样更适合"此刻就要答案"的场景,缺点是需要额外安装、且对内核有少量要求。
排错工具再好也是人肉触发,工程化做法是把指标接进持续监控。标准链路是 JVM 通过 JMX 暴露运行指标(内存池水位、GC 计数与耗时、线程数),由 exporter 转成时序数据,Prometheus 抓取,Grafana 呈现,超过阈值告警——告警响了,人才需要用本章工具介入。很多框架与发行版(Spring Boot Actuator 等)提供了开箱的指标端点,配置成本很低。
分层记法:监控回答"什么时候、哪个指标异常"(发现),JFR 回答"异常时段内部发生了什么"(回放),Async-Profiler 回答"哪个方法占大头"(定位),四件套回答"此刻现场细节"(取证)。四层各司其职,缺了监控层你就是人肉告警器,缺了回放层你只有猜测。
⚠️ 常见坑:JFR 当成零开销无限开。settings=profile 的方法采样在超高 QPS 服务上仍有可感知成本,maxsize 不设又可能吃磁盘。生产常驻建议 default 套餐加滚动上限,事故临时切 profile——开关成本远低于常驻成本。
⚠️ 常见坑:火焰图只看最上面一格。最上层只是"被调用者",宽度才是权重的度量;只看顶层容易被大量小函数拼出的宽顶误导,要找的是"宽且平"的那层,并结合业务上下文判断是否合理。
💡 关键直觉:连续观测的意义是"让证据等在事故发生之前"。四件套的快照像到场的照片,JFR 像行车记录仪——事故的完整过程,往往只有录像能还原。对无法稳定复现的问题,常驻记录是唯一可靠的取证手段。
工具备齐,下一节把四件套、JFR、火焰图串成四套完整的临床套路:内存泄漏、GC 风暴、锁竞争、CPU 飙升——每条病案从症状走到凶手,一步不跳。