深度探索JFR - JFR详细介绍与生产问题定位落地 - 1. JFR说明与启动配置(上)

简介: 深度探索JFR - JFR详细介绍与生产问题定位落地 - 1. JFR说明与启动配置(上)
本文基于 OpenJDK 11 并涉及一些之后版本的特性,非 OpenJDK 11 的特性会被特殊标记出来


什么是 JFR?


我们都知道,黑匣子是用于记录飞机飞行和性能参数的仪器。在飞机出问题后,用于定位问题原因。JFR 就是 Java 的黑匣子。


JFR 是 Java Flight Record (Java飞行记录) 的缩写,是 JVM 内置的基于事件的JDK监控记录框架。这个起名就是参考了黑匣子对于飞机的作用,将Java进程比喻成飞机飞行。顾名思义,这个记录主要用于问题定位和持续监控。


如果是利用默认配置启动这个记录,性能非常高效,对于业务影响很小,因为这个框架本来就是用来长期在线上部署的框架。这个记录可以输出成二进制文件,用户可以指定最大记录时间,或者最大记录大小,供用户在需要的时候输出成文件进行事后分析。


JFR 的前身也是 JFR,只不过这个 J 不是 Java 而是 JRockit。在 JRockit 虚拟机时代,就有这样一个工具用来记录 Java 虚拟机运行时各项数据。在 Oracle 收购 Sun 公司之后,Hotspot 虚拟机时代,也一直延续了这个工具: - JFR 0.9 版本对应 JDK 7 和JDK 8:JDK 7u40 之后,实现了和 JRockit Flight Recorder 一样的功能,并添加了各项数据配置,用来打开或者关闭一些统计数据功能。而且,在 JDK 8u40 之后,可以在运行时灵活地打开关闭 JFR。 - JFR 1.0 版本对应 JDK 9 和 JDK 10: 在这一版本之后,增加了 JFR 事件接口,用户可以生产或者消费某种事件。 - JFR 2.0 版本对应 JDK 11,这一版本就是我们今天要详细讨论说明。


这里我们先来列出一些些关于JFR更新与bug信息的链接: - JEP 328: Flight Recorder(Release in JDK 11): https://openjdk.java.net/jeps/328 - hotspot虚拟机JFR相关bug:https://bugs.openjdk.java.net/browse/JDK-8240430?jql=project%20in%20(JDK)%20AND%20component%20in%20(hotspot)%20AND%20Subcomponent%20in%20(jfr) - JEP 349: JFR Event Streaming(Release in JDK 14):https://openjdk.java.net/jeps/349


为什么用 JFR?


因为某些异常很难在开发测试阶段发现,需要在生产环境才会出这些问题。为了能在生产问题发生后,更好的定位生产问题,JDK 提供了这样一个可以长期开启,对应用影响很小的持续监控手段。官方说,目标是开启 JFR 监控(默认配置),对性能的影响在1%之内,对JVM Runtime 和 GC,OS 以及 Java 库进行全方位的监控。


这里放出一个本人开启默认配置的 JFR 监控后,性能对比,JFR是在19:40开启的:


image.png


可以看出,在19:40开启default,之后请求数量回归峰值之前,Load基本和之前一样,可以视为无影响。

再放出一个本人在同一个微服务另一个实例同一时间开启 profile 配置的 JFR 监控后,性能对比,同样是在19:40开启:


image.png


rofile的情况下,峰值load相较于default的峰值load高了很多。profile配置官方说大概影响2%的性能,但是实际上,这个影响,尤其是频繁发生内存分配的微服务接口应用,影响绝对不止2%,而且profile的确采集的东西要比默认配置的多很多(这个我们后面会详细说,为什么负载会高的原因也会在后面说),所以,线上系统不推荐长期跑profile。

JFR,具有以下关键的特性: - 低开销(在配置正确的情况下),可在生产环境核心业务进程中始终在线运行。当然,也可以随时开启与关闭。 - 可以查看出问题时间段内进行分析,可以分析 Java 应用程序,JVM 内部以及当前Java进程运行环境等多因素。 - JFR基于事件采集,可以分析非常底层的信息,例如对象分配,方法采样与热点方法定位与调用堆栈,安全点分析与锁占用时长与堆栈分析,GC 相关分析以及 JIT 编译器相关分析(例如 CodeCache ) - 完善的 API 定义,用户可以自定义事件生产与消费。

JFR 的核心 - 事件 Event 说明

在 JFR中,一切皆为 Event: - 任意JVM行为都是一个Event,例如类加载也是一个 Event,对应 Class Load Event - 开启 JFR 记录的原因也是一个Event,对应的就是 Recording Reason Event - 就算是有 Event 丢失,他也是一个 Event,对应 Data Loss Event

这些 Event 在某些特定的时间点产生,每个事件都有名称,产生时间戳还有 Event 数据体组成。Event 数据体不同的 Event 数据不同,例如 CP U负载,Event 发生之前还有之后的 Java 堆大小, 获取锁的线程 ID 等等。还有一点比较有意思的是,大部分的 Event,都有 Event 是在哪个线程发生的,Event 发生的时候这个线程的调用栈,Event 的持续时间。这就非常有用了,利用这些信息,我们可以回溯 Event 发生当时的情况。

Event 按照采集方式可以分为三种:

  1. Instant Event:顾名思义,这种 Event 在发生时就立刻采集。例如:Throw Exception Event 还有 Thread Start Event,类似于这种在某一时刻发生的 Event
  2. Duration Event:这种 Event 需要耗费一些时间,在完成的时候会记录。对于这种类型的 Event,可以设置一个时间限制,超过这个时间限制的才会记录。例如 GC Event,Thread Sleep Event。
  3. Sample Event(或者是Requestable Event):按照一定的频率采集,这个频率是可以配置的。例如 Thread Dump Event,Method Sampling Event

由于 JFR 会采集很多很多的数据,为了效率,最好配置自己感兴趣的事件采集,并且对于 Duration Event 设置时间限制,一般我们对于时间短的事件并不关心。

Event 会被写入 .jfr 的二进制文件(二进制文件对于应用来说读写效率最高)中,以 little endian base 128 的形式编码,这里我们用一个 Event 举个例子:

Class Load Event

0000FC10 : 98 80 80 00 87 02 95 ae e4 b2 92 03 a2 f7 ae 9a 94 02 02 01 8d 11 00 00
  • 0000FC10: 文件位置
  • 98 80 80 00: Event 大小
  • 87 02: Event ID
  • 95 ae e4 b2 92 03: 时间戳
  • a2 f7 ae 9a 94 02: 持续时间
  • 02: 线程 ID
  • 01: 堆栈 ID
  • PayLoad(每种 Event 的 field 不同):
  • 8d 11: 加载的类
  • 00 : 定义类的 ClassLoader
  • 00 : 初始化类的 ClassLoader

这里仅仅是举个例子,实际使用中,我们肯定不会去这么看每个 Event 的,而是通过可视化工具 JMC 去看,这个我们后面会讲到。至于 Event 有哪些种类,也会在后面的章节涉及到。

那么这些Event是如何产生,如何记录保持高效的呢?


JFR如何实现的低延迟与低性能损耗


首先,Event肯定是多线程产生的,这点显而易见。如果 Event 记录要保证全局有序,那么肯定需要多线程向一个指定队列或者缓存输出,那么不可避免的会涉及到锁争用,这样是很低效的。 Event本身带时间戳,那么可不可以在最后读取的时候进行排序?在一个线程内,生成的 Event 肯定是有序的;那么多线程产生的 Event, 就可以看成一个又一个的有序集合。最后,针对这些有序集合的每个元素进行整体排序,算法上快很多。所以我们没有必要在 Event 产生的时候就进行整体排序。


在 JFR 中,所有的 Event (包括通过JFR API产生的 Event 还有 JVM 产生的 EVENT),会先存储到每个线程自己的 Thread Buffer 中;在这个 Buffer 满了之后,会将 Buffer 的内容刷入 Global Buffer 中;Global Buffer 是一个环形 Buffer,保存着所有线程发送来的 Thread Buffer 中的内容。当这个环形 Buffer 存储到达上限之后,根据配置,会选择丢弃或者刷入文件


可以看出,不同的 Buffer 之间的数据不会有任何重叠。并且某一块数据,要么就是在内存中,要么就是在磁盘上,不会两个地方都存在,那么这样会带来数据丢失的问题: - 首先,在断电的时候或者操作系统强制重启的时候,还未写入磁盘的 Event 会丢失。 - 如果只是强制 kill -9掉了Java 进程,那么刷入文件写入高速缓冲的 Event 不会丢失,但是 Global Buffer 中还有 Thread Buffer 中的数据会丢失。同样的,如果JVM崩溃了,这些内存Buffer中的数据也会丢失。正常退出,或者应用异常但是JVM正常退出的,数据不会丢失。 - 采集的数据在可见之前可能会有很小的延迟。例如数据在从 Thread Buffer 刷入 Global Bufeer 的时候, 你如果去 dump JFR 的数据,可能这部分数据会被忽略而导致看不到。 - 最后一点,任何情况下导致在从 Global Buffer 刷入磁盘不够快的时候,这时候要刷入磁盘的数据可能被丢弃。当发生这种情况是,就会记录下数据丢失事件,这个事件包括是那块时间的数据丢掉了。通过 JFR 的日志也能看到这个信息。


image.png


开启JFR记录


可以通过启动参数配置并且启用 JFR,也可以通过启动参数在 JVM 进程启动的时候就启动 JFR,或者是利用 jcmd 工具,动态启用或者关闭 JFR。




相关文章
|
3月前
|
监控 数据可视化 Java
jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
53 1
|
Arthas Cloud Native Java
是谁在调用我?使用 arthas+jprofiler 做复杂链路分析
Arthas 是阿里巴巴开源的应用诊断利器,提供了 profiler 命令,可以生成热点火焰图。通过采样录制调用链路来做性能分析,极大提升了线上排查性能问题的效率。
是谁在调用我?使用 arthas+jprofiler 做复杂链路分析
|
存储 安全 Java
深度探索JFR - JFR定位线上问题实例 - JFR导致的雪崩问题定位与解决
深度探索JFR - JFR定位线上问题实例 - JFR导致的雪崩问题定位与解决
深度探索JFR - JFR定位线上问题实例 - JFR导致的雪崩问题定位与解决
|
XML 存储 监控
java日志(系统运行监控)
.什么是日志? 记录系统运行的过程和详情,并可以进行永久存储
201 0
java日志(系统运行监控)
|
缓存 监控 Oracle
JFR诊断增强学习笔记
快速学习JFR诊断增强
132 0
JFR诊断增强学习笔记
|
Java Linux Windows
深度探索JFR - JFR详细介绍与生产问题定位落地 - 1. JFR说明与启动配置(下)
深度探索JFR - JFR详细介绍与生产问题定位落地 - 1. JFR说明与启动配置(下)
深度探索JFR - JFR详细介绍与生产问题定位落地 - 1. JFR说明与启动配置(下)
|
XML Java BI
JFR详细介绍与生产问题定位落地 - 2. 通过实例了解JMC 与 Event 结构与详细配置
JFR详细介绍与生产问题定位落地 - 2. 通过实例了解JMC 与 Event 结构与详细配置
JFR详细介绍与生产问题定位落地 - 2. 通过实例了解JMC 与 Event 结构与详细配置
|
Arthas 缓存 开发框架
JFR详细介绍与生产问题定位落地 - 3. 各种Event详细说明与JVM调优策略(2)
JFR详细介绍与生产问题定位落地 - 3. 各种Event详细说明与JVM调优策略(2)
JFR详细介绍与生产问题定位落地 - 3. 各种Event详细说明与JVM调优策略(2)
|
消息中间件 监控 算法
JFR详细介绍与生产问题定位落地 - 3. 各种Event详细说明与调优策略(1)
JFR详细介绍与生产问题定位落地 - 3. 各种Event详细说明与调优策略(1)
JFR详细介绍与生产问题定位落地 - 3. 各种Event详细说明与调优策略(1)
|
存储 设计模式 缓存
深度探索JFR - 3. 各种Event详细说明与JVM调优策略(3)
深度探索JFR - 3. 各种Event详细说明与JVM调优策略(3)
深度探索JFR - 3. 各种Event详细说明与JVM调优策略(3)