Administrator
发布于 2023-11-28 / 4032 阅读
42

JFR 事件流与持续性能分析

不想为了监控拖垮生产

之前用 VisualVM 连生产抓采样,JMX 一开 CPU 就飘,GC 行为都变了,抓到的数据还不可信。后来发现 JDK 11 起的 JFR(Java Flight Recorder)开销极低,而且 JDK 14 起的 Event Streaming 能实时订阅事件,不用等 dump 文件。我们把它接进了监控管线,常开录制,出问题直接回放,再没为监控影响过业务。

JFR 事件类型

JFR 内置几百种事件,分两类:

  • 持续型:如 jdk.CPUUsage、jdk.GCHeapSummary,周期性记录,采样频率可配;
  • 点状型:如 jdk.ExceptionThrow、jdk.SocketRead,发生时才记,开销按需。

我们用得最多的是 jdk.GarbageCollection、jdk.ThreadPark、jdk.ObjectAllocationInNewTLAB,能看出 GC 节奏和锁竞争。默认 continuous profile 配置下,开销通常低于 1%,所以可以放心常开。

开启录制

# 启动即开,profiling 配置,磁盘保留 1 天
java -XX:StartFlightRecording=disk=true, \
     filename=app.jfr, maxsize=200m, maxage=1d, \
     settings=profile \
     -jar app.jar

maxsize 防止磁盘写爆,maxage 控制保留时长。我们配了 200m 上限、保留 1 天,足够复盘最近的问题。

JFR Event Streaming 实时订阅

过去要等录完才能分析。JDK 14 起的 jdk.jfr.consumer 能在事件产生的同时读取。我们写了个常驻采集器,把异常事件实时推到 Prometheus,这样异常突增能立刻告警:

try (var es = EventStream.openRepository(Path.of("/tmp/jfr"))) {
    es.onEvent("jdk.ExceptionThrow", e -> {
        String ex = e.getClass("exceptionClass").getName();
        exceptionCounter.labels(ex).inc();
    });
    es.onEvent("jdk.GarbageCollection", e ->
        gcPause.observe(e.getDuration().toMillis()));
    es.start();
}

也可以直连正在运行的 JVM:EventStream.openFlightRecorderStream(),无需落盘,事件一边产生一边流过来,延迟在毫秒级,适合做实时看板。

生产环境的真实开销

我们在 4 核 8G 的订单服务开 profile 级录制,QPS 5000 下 CPU 仅多 0.8 个百分点,GC 停顿没变化。对比之前 JMX 采样带来的 6% 抖动,JFR 几乎无感。一个月下来落盘 JFR 约 6GB,定期清理即可,还能在故障后用 jfr 命令回放分析,比如定位某次 Full GC 前到底谁在大量分配。

写在后面

现在回头看,《JFR 事件流与持续性能分析》本身不算多难,难的是线上真出问题那十分钟里的判断。经验都是这么来的。

参考