JFR:Spring Boot 应用的性能诊断利器

GC 停顿、内存泄漏、接口响应变慢------线上服务出问题的时候,你是否也曾对着监控面板发呆,不知道根因在哪?

今天聊聊 Java 自带的性能诊断神器 JFR(Java Flight Recorder),配合 Spring Boot 使用,效果翻倍。

JFR 是什么

JFR 是 JDK 内置的性能采集工具,知道的人不多,但用过的人都说是「真香」。

几个核心优势:

  • 开销可控:通过事件开关控制采集粒度,精准场景下开销可压到极低
  • 事件丰富:GC、线程、IO、锁、CPU、异常......100+ 种事件类型
  • 历史回溯:录制文件可以离线分析,事后定位没问题
  • 持续录制:支持后台常驻,出问题随时有数据兜底

JDK 8 及之前 JFR 是商业特性,JDK 9+ 免费开源。

生产环境启用前建议确认:磁盘空间充足、开启了 JFR 权限控制、采集的事件范围符合需求。

Spring Boot 启用 JFR

方式一:启动参数(最简单)

bash 复制代码
java -XX:StartFlightRecording:filename=recording.jfr,duration=60s -jar app.jar

更多参数配置:

bash 复制代码
java -XX:StartFlightRecording=\
    filename=app-recording.jfr,\
    dumponexit=true,\
    maxsize=500M,\
    maxage=1d,\
    settings=profile -jar app.jar

参数说明:

参数 含义
filename 录制文件保存路径
dumponexit JVM 退出时自动 dump
maxsize 单文件最大 size
maxage 最老数据的保留时间
settings 模板(production/profile)

方式二:API 动态控制

Spring Boot 可以通过 JMX 远程控制 JFR 开始/停止:

java 复制代码
@Service
public class JfrController {

    private final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
    private static final String JFR_BEAN_NAME = "com.oracle.jdk:jfrType=FlightRecorder";

    @PostMapping("/jfr/start")
    public String start(@RequestParam String filename) throws Exception {
        ObjectName name = new ObjectName(JFR_BEAN_NAME);
        Map<String, String> settings = Map.of(
            "jdk.JavaMonitorEnter#enabled", "true",
            "jdk.SocketRead#enabled", "true"
        );
        mBeanServer.invoke(name, "startRecording",
            new Object[]{filename, settings},
            new String[]{String.class.getName(), Map.class.getName()}
        );
        return "started: " + filename;
    }

    @PostMapping("/jfr/stop")
    public String stop() throws Exception {
        ObjectName name = new ObjectName(JFR_BEAN_NAME);
        mBeanServer.invoke(name, "stopRecording", new Object[]{}, new String[]{});
        return "stopped";
    }
}

方式三:Actuator 集成

Spring Boot Actuator 也能暴露 JFR 端点:

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,jfr
  jfr:
    enabled: true
    directory: /var/log/jfr

录制文件怎么分析

JDK Mission Control (JMC)

这是官方提供的 GUI 分析工具,JDK 11+ 自带。

bash 复制代码
jmc

打开录制文件后,重点看这几个视图:

1. Recording View - 事件列表和分布
2. Code Path - 热点方法调用链
3. Memory - GC 和对象分配
4. Threads - 锁竞争、线程状态
5. I/O - 文件和网络延迟

命令行快速分析

bash 复制代码
# 概览
jfr summary recording.jfr

# 只看某类事件
jfr print --events "jdk.GC*,jdk.JavaMonitorEnter" recording.jfr

# 找出耗时最长的调用
jfr print --events "jdk.ExecutionSample" recording.jfr | head -30

代码分析脚本

写个脚本批量处理多个录制文件:

java 复制代码
public class JfrAnalyzer {

    public static void main(String[] args) throws Exception {
        var file = new File(args[0]);
        Map<String, Long> durations = new HashMap<>();
        Map<String, Integer> counts = new HashMap<>();

        try (var rs = RecordingFile.open(file)) {
            for (var event : rs) {
                String name = event.getEventType().getName();
                counts.merge(name, 1, Integer::sum);
                if (event.hasValue("duration")) {
                    long ns = event.getDuration("duration").toNanos();
                    durations.merge(name, ns, Long::sum);
                }
            }
        }

        // 打印结果
        System.out.println("=== Most Frequent ===");
        counts.entrySet().stream()
            .sorted((a, b) -> b.getValue() - a.getValue())
            .limit(10).forEach(e ->
                System.out.printf("  %s: %d%n", e.getKey(), e.getValue()));

        System.out.println("=== Slowest ===");
        durations.entrySet().stream()
            .sorted((a, b) -> (int)(b.getValue() - a.getValue()))
            .limit(10).forEach(e ->
                System.out.printf("  %s: %d ms%n", e.getKey(), e.getValue() / 1_000_000));
    }
}

实战场景

GC 停顿排查

bash 复制代码
java -XX:StartFlightRecording:\
    filename=gc.jfr,\
    events="jdk.GC*,jdk.YoungGarbageCollection,jdk.OldGarbageCollection" -jar app.jar

JMC 里重点看:

  • GC Pause 总时长和频率
  • Young GC 频率是否过高
  • 对象晋升(Promotion)是否频繁

接口慢请求定位

先写个简单的耗时日志 AOP:

java 复制代码
@Aspect
@Component
public class TimingAspect {

    @Around("@annotation(GetMapping)")
    public Object trace(ProceedingJoinPoint p) throws Throwable {
        var start = System.currentTimeMillis();
        try {
            return p.proceed();
        } finally {
            var cost = System.currentTimeMillis() - start;
            if (cost > 1000) {
                System.out.printf("[SLOW] %s: %d ms%n", p.getSignature(), cost);
            }
        }
    }
}

慢请求出现后,配合 JFR 定位具体瓶颈:

JFR 事件 对应问题
jdk.ExecutionSample CPU 热点
jdk.FileRead/Write 磁盘 IO 慢
jdk.SocketRead/Write 网络 IO 慢
jdk.JavaMonitorEnter 锁等待

内存泄漏

开启对象分配事件:

复制代码
jdk.ObjectAllocationInNewTLAB
jdk.ObjectAllocationOutsideTLAB
jdk.OldGarbageCollection

老年代回收频率异常升高 + 堆大小持续增长,基本就是泄漏了。配合 jfr print --events "jdk.OldGarbageCollection" 看回收模式,再导出堆 dump 定位泄漏对象。

生产环境注意事项

1. 资源限制

bash 复制代码
-XX:FlightRecorderOptions=maxchunksize=100M,memorysize=50M

2. 持续录制 + 轮转

bash 复制代码
java -XX:StartFlightRecording=\
    filename=/opt/jfr/app.jfr,\
    dumponexit=true,\
    maxage=7d,\
    maxsize=1G,\
    settings=production -jar app.jar

3. 权限控制

yaml 复制代码
export COM_SUN_JDK_JFR_OPTIONS="security-manager=true"

4. 告警联动

java 复制代码
@Scheduled(fixedRate = 60000)
public void alertIfNeeded() {
    // 读取 JFR 事件,异常时推送到告警平台
}

总结

JFR 不是什么新东西,但确实是「平时用不上,出事能救命」的工具。

Spring Boot 集成后,启用成本不高。生产环境按需开启,配合资源限制和轮转策略,遇到问题直接看录制文件,比猜日志高效得多。

几个建议:

  • 先记几个常用 JFR 事件名,用到再查文档
  • 持续录制 + 7 天轮转,出问题有数据可查
  • 配合 JMC 可视化,分析效率更高
相关推荐
爱吃山竹的大肚肚2 小时前
微服务间通过Feign传输文件,处理MultipartFile类型
java·spring boot·后端·spring cloud·微服务
_周游2 小时前
Java8 API文档搜索引擎_使用内存缓冲区优化
java·搜索引擎·intellij-idea
twj_one2 小时前
java中23种设计模式
java·开发语言·设计模式
tsyjjOvO3 小时前
JDBC(Java Database Connectivity)
java·数据库
qq_12498707533 小时前
基于springboot的尿毒症健康管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·spring·毕业设计·计算机毕业设计
编程彩机3 小时前
互联网大厂Java面试:从Spring Boot到微服务优化场景解析
spring boot·分布式事务·微服务架构·java面试·技术解析
猿小羽3 小时前
Flyway + Spring Boot:实现数据库迁移的最佳实践
spring boot·编程·flyway·最佳实践·数据库迁移
黎子越3 小时前
python相关练习
java·前端·python
电商API&Tina3 小时前
电商数据采集 API 接口 全维度解析(技术 + 商业 + 合规)
java·大数据·开发语言·数据库·人工智能·json