随着应用系统日益复杂,运行时的性能监控和问题排查需求愈发重要。Java Flight Recorder(JFR) 是一个被广泛低估但极为强大的 JVM 内建性能监控和诊断工具。它可以用最小的性能开销,收集 JVM 运行时数据,帮助开发者定位如 GC 频繁、线程死锁、内存泄漏等问题。
本文将系统介绍 JFR 的能力、使用场景、与其他监控方式的对比,以及如何在 Spring 项目中集成和使用。
一、什么是 Java Flight Recorder?
JFR 最早是 Oracle JDK 的商业功能,自 JDK 11 起在 OpenJDK 中也成为开源组件,默认集成在 JVM 中。它通过埋点的方式在 JVM 各个子系统中记录事件数据,这些事件被高效编码为二进制格式,几乎不影响应用运行。
支持监控的核心指标包括:
- CPU 和线程使用情况
- 锁竞争
- 内存分配与垃圾回收
- 类加载和卸载
- IO 操作
- 方法调用热度
典型使用场景
场景 | 说明 |
---|---|
🐢 系统运行缓慢 | 分析 GC 是否频繁、方法执行是否热点、锁是否阻塞 |
⚠️ 内存问题 | 观察内存分配速率、对象存活周期、GC 压力 |
🧵 线程问题 | 排查死锁、线程饥饿、阻塞点(如数据库、网络I/O) |
🔒 锁竞争严重 | 查看哪个代码块导致锁膨胀或线程争用 |
📈 压测与调优 | 在模拟负载下采集运行数据,做性能瓶颈分析 |
🐛 异常频发但难复现 | 捕捉异常抛出位置和频率(适用于"偶现问题") |
📦 云原生 / 容器 | 配合 Cryostat、Grafana JFR 插件进行可视化与远程采集 |
二、JFR 与其他监控方式的对比
特性 | JFR | Prometheus + Grafana | JMX | APM(如 Skywalking、NewRelic) |
---|---|---|---|---|
数据粒度 | 精细(方法级) | 粗粒度(指标级) | 中等(线程/GC) | 多数为中等 |
开销 | 极低(生产可用) | 较低 | 中等 | 中等偏高 |
是否需接入 | 无需额外依赖 | 需要手动埋点或 exporter | 内建 | 需 agent 或 SDK 接入 |
支持事件类型 | JVM 全生命周期 | 应用自定义指标 | JVM 部分指标 | HTTP、SQL、GC 等 |
可离线分析 | ✅ | ❌(实时) | ✅ | 依赖后端 |
可视化工具 | JMC, IntelliJ | Grafana | VisualVM | 各自控制台 |
✅优势总结:
优势 | 说明 |
---|---|
🧩 原生集成 | JFR 是 JVM 的一部分,无需安装额外 agent |
🪶 极低开销 | 精心设计的 ring buffer + 高效 native 事件采集 |
🕒 可录制历史事件 | 类似"黑匣子",便于偶发问题追踪 |
🧵 多维度分析 | 支持线程、GC、方法采样、异常、锁争用等多层数据 |
📦 配合 JMC / IntelliJ | 图形化分析体验好,可快速定位性能瓶颈 |
☁️ 云原生适配性 | 与 Cryostat、Grafana 等工具良好集成,适用于容器环境 |
👎 JFR 的局限
局限 | 描述 |
---|---|
❌ 不支持 Java 8 以下 | JFR 是从 JDK 7u40 开始出现,但 JDK 11 后才开源且默认启用 |
❌ 不分析对象引用关系 | 不能像 MAT(Memory Analyzer Tool)那样做对象泄漏路径分析 |
❌ 无法跨语言监控 | 不适用于非 Java 服务或混合语言系统 |
三、JFR 埋点原理与事件机制
JFR 的"埋点"并不是用户代码中的埋点,而是 JVM 在 HotSpot 中对如下位置进行插桩和事件发射:
- GC:记录每次 GC 的时间与暂停
- Lock:记录每次对象锁竞争(ContendedMonitorEnter)
- Allocation:记录新对象分配
- Thread:线程切换、阻塞、唤醒
- Custom Event:允许我们自定义事件(JDK 14 起稳定)
相比于 Prometheus metrics 的数值快照埋点,JFR 更像是时间线上的事件流,能够记录"谁做了什么"而不是"某个时刻有多少"。
四、如何在 IntelliJ IDEA 中使用 JFR
1. 无需额外插件(Ultimate 版本)
-
在运行配置(Run Configuration)中启用 JVM 参数:
rubyjava -XX:StartFlightRecording=filename=recording.jfr,duration=5m -jar demo.jar
2. 使用 JDK Mission Control 可视化分析
- 下载地址:JMC 官网
- 支持浏览 JFR 文件、展示火焰图、线程视图、锁视图等。
五、Spring 项目中使用 JFR 的实战案例
1. 添加启动参数
修改 pom.xml
确保使用 JDK 17+ 编译,同时在 application.properties
中无需配置。
启动命令示例:
bash
java -XX:StartFlightRecording=filename=recording.jfr,dumponexit=true,settings=profile -jar demo.jar
2. 示例代码
1. GC 压力和锁竞争模拟
java
@RestController
public class MonitorController {
// 模拟对象分配造成 GC 压力
@GetMapping("/allocate")
public String allocate() {
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(new byte[1024 * 1024]); // 分配1MB
}
return "Allocated memory.";
}
private final Object lock = new Object();
// 模拟线程锁竞争
@GetMapping("/lock")
public String lockTest() throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
pool.submit(() -> {
synchronized (lock) {
try {
Thread.sleep(2000); // 持锁时间
} catch (InterruptedException ignored) {}
}
});
}
return "Lock test started.";
}
}
2. 代码埋点
java
@Label("Order Processing Event")
public class OrderProcessingEvent extends Event {
@Label("Order ID")
public String orderId;
@Label("Elapsed Time (ms)")
public long elapsedTime;
}
@RestController
@RequestMapping("/orders")
public class OrderController {
private final MeterRegistry meterRegistry;
public OrderController(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@PostMapping("/{id}/process")
public String processOrder(@PathVariable String id) throws InterruptedException {
// === JFR 自定义事件埋点 ===
OrderProcessingEvent event = new OrderProcessingEvent();
event.begin();
long start = System.currentTimeMillis();
// 模拟业务处理耗时
Thread.sleep((long) (Math.random() * 1000));
event.orderId = id;
event.elapsedTime = System.currentTimeMillis() - start;
event.end();
event.commit();
// === Micrometer 监控指标埋点 ===
Timer.builder("order.process.time")
.description("Time to process orders")
.tag("orderId", id)
.register(meterRegistry)
.record(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);
meterRegistry.counter("order.process.count").increment();
return "Order " + id + " processed.";
}
}
3. 运行后使用 Mission Control 分析
- 打开
.jfr
文件 - 查看 Memory 面板中内存分配趋势
- 查看 Locks 面板中锁竞争对象与线程栈
1. 锁竞争查看
通过线程的直方图和火焰图可以明确了解线程休眠时间和线程状态
2. 内存分配情况
应用运行过程中的内存分配和GC情况可以通过分析JFR文件得出,而且比起传统堆栈存储文件的优势是我们可以通过火焰图和内存使用量统计来直观看出应用中导致内存占用过大和GC频繁的业务场景
3. 事件埋点分析
我们可以通过列表和图标观测我们的程序埋点,它可以记录事件类型、事件所在线程、事件的执行时间和执行耗时,并展示我们自定义的事件信息

六、应用场景
适合使用 JFR 的典型场景包括:
- ❗ 排查 频繁 GC 或内存泄漏
- ⏱️ 分析 慢接口 的方法热点
- 🔐 检测 锁竞争瓶颈
- 📉 评估 线程状态(如阻塞、饥饿)
- 🔍 离线收集生产数据用于问题复现
七、总结
Java Flight Recorder 是现代 Java 应用性能诊断的重要工具。它的最大亮点在于:无需侵入应用代码、运行时开销小、事件粒度细、可视化支持完善。相比 APM 或 metrics 工具,JFR 更适合精准问题定位,特别是在生产环境中捕捉难以复现的异常行为。
如果你还未尝试过 JFR,现在是时候将它纳入你的性能排查工具链中了。