2025-03-11 13:55:26 +08:00
|
|
|
|
### 出处
|
|
|
|
|
|
|
|
|
|
[文章出处](https://zhuanlan.zhihu.com/c_1264859821121355776) [关注大佬](https://www.zhihu.com/people/zhxhash) 我只是对大佬的文章内容做一个笔记,加深记忆和理解。
|
|
|
|
|
|
2025-03-11 11:04:19 +08:00
|
|
|
|
### 什么是JFR?
|
|
|
|
|
JFR是**Java Flight Record**(Java飞行记录)的缩写,是JVM内置的基于事件的监控记录框架。这个起名参考了黑匣子对于飞机的作用,将Java进程比喻成飞机飞行。顾名思义,JFR主要用于问题定位和持续监控。
|
|
|
|
|
|
|
|
|
|
### JFR版本
|
|
|
|
|
**JFR 0.9**版本对应**JDK7和8**,在8u40之后,可以在运行时开启/关闭。**JFR 1.0**版本对应**JDK9和10**,在这一版本之后,增加了JFR事件接口,用户可以生产或者消费某种事件。**JFR 2.0**版本对应**JDK11**,下面的参数都是基于这一版本。
|
|
|
|
|
|
|
|
|
|
### 为什么用JFR?
|
|
|
|
|
为了在生产环境更好的定位问题。JDK提供了一个可以长期开启,对应用影响很小的持续监控手段,官方的目标是开启JFR监控(默认配置,非profile)**对性能的影响在1%以内,对JVM Runtime、GC、OS以及Java库进行全方位的监控。**
|
|
|
|
|
|
|
|
|
|
### JFR的核心(Event)
|
2025-03-11 11:16:07 +08:00
|
|
|
|
在JFR中一切皆为Event,任意JVM行为都是一个Event,例如:
|
2025-03-11 11:04:19 +08:00
|
|
|
|
|
|
|
|
|
- 类加载,Class Load Event
|
|
|
|
|
- 开启JFR记录,Recording Reason Event
|
2025-03-11 11:16:07 +08:00
|
|
|
|
- 就算是 Event 丢失,也是一个 Data Loss Event
|
2025-03-11 11:04:19 +08:00
|
|
|
|
|
2025-03-11 11:16:07 +08:00
|
|
|
|
Event 在某些特定的时间点产生,**由名称、时间戳、Event 数据体组成。**不同的 Event 数据体不同(例如 CPU 负载,Event 前后的 Java 堆大小,获取锁的线程 ID 等)
|
|
|
|
|
|
|
|
|
|
> 大部分的 Event,都有 Event 是在哪个线程发生的、线程的调用栈、Event 持续时间,利用这些信息,我们可以回溯 Event 发生当时的情况。
|
|
|
|
|
|
|
|
|
|
### Event类型
|
|
|
|
|
Event 按照采集方式可以分为三种:
|
|
|
|
|
|
|
|
|
|
- Instant Event,这种 Event 在发生时就立刻采集。例如:Throw Exception Event、Thread Start Event,这种在某一时刻发生的 Event
|
|
|
|
|
- Duration Event,这种 Event 在完成的时候记录。因为需要耗费一些时间,但可以设置一个时间限制,超时才记录。例如:GC Event、Thread Sleep Event
|
|
|
|
|
- Sample Event(Requestable Event)这种 Event 按照一定的频率采集。频率是可以配置的,例如:Thread Dump Event、Method Sampling Event
|
|
|
|
|
|
|
|
|
|
由于 JFR 会采集很多很多的数据,为了效率,最好配置自己感兴趣的事件采集。并且对于 Duration Event 设置时间限制,一般我们对于时间短的事件并不关心。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Event存储
|
|
|
|
|
Event 会被写入`.jfr`的二进制文件中,以`little endian base 128`的形式编码,以 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
|
|
|
|
|
- 8d 11: 加载的类
|
|
|
|
|
- 00 : 定义类的 ClassLoader
|
|
|
|
|
- 00 : 初始化类的 ClassLoader
|
|
|
|
|
|
|
|
|
|
> 实际使用中,通过可视化工具**JMC**查看`.jfr`文件
|
|
|
|
|
|
2025-03-11 13:55:26 +08:00
|
|
|
|
### 如何实现的低延迟、低性能损耗?
|
|
|
|
|
|
|
|
|
|
Event 是多线程产生的,如果 Event 记录要保证全局有序,那么肯定需要多线程向一个指定队列或者缓存输出,那么不可避免的会涉及到锁争用,这样是很低效的。而 Event 本身带时间戳,所以记录时不需要排序,将每个线程内的记录,合并成一个集合后再进行排序高效得多。
|
|
|
|
|
|
|
|
|
|
<img src='https://pic3.zhimg.com/v2-5530b8a77d0d45ac12dd879ccf7afce8_1440w.jpg'/>
|
|
|
|
|
|
|
|
|
|
1. 所有的 Event 会先存储到每个线程自己的 Thread Buffer(默认8KB,这是一个经验值)
|
|
|
|
|
2. Thread Buffer 满了之后刷入 Global Buffer(可配置)
|
|
|
|
|
3. Global Buffer 满了之后会选择丢弃或者刷入文件(可配置)
|
|
|
|
|
|
|
|
|
|
*Thread Buffer 中的数据要么在内存中,要么就在磁盘里。不会两个地方都存在。*
|
|
|
|
|
|
|
|
|
|
#### JFR记录数据丢失?
|
|
|
|
|
|
|
|
|
|
- 断电、操作系统强制重启
|
|
|
|
|
- kill -9 了 Java 进程
|
|
|
|
|
- JVM 崩溃
|
|
|
|
|
|
2025-03-11 14:00:03 +08:00
|
|
|
|
以上三种情况,刷入文件的 Event 不会丢,但内存里的 Global Buffer、Thread Buffer 会丢。对于JVM正常退出(含应用异常但JVM正常退出)的情况,数据不会丢。
|
2025-03-11 11:04:19 +08:00
|
|
|
|
|
2025-03-11 14:00:03 +08:00
|
|
|
|
⚠️数据在从 Thread Buffer 刷入 Global Bufeer 的时候, 去 dump JFR 的数据,可能这部分数据会被忽略而导致看不到。
|
|
|
|
|
|
|
|
|
|
⚠️从 Global Buffer 刷入磁盘不够快的时候,这时候要刷入磁盘的数据可能被丢弃。此时会记录下 Data Loss Event 包含了哪块时间的数据丢了,通过 JFR 日志也能看到这个信息。
|