Event 採集詳細配置
目前,JDK 11 一共有136個 Event 採集配置。這裏會比較詳細的去看每一個Event,並說明基本應用,建議配置。如果 default.jfc 中沒有打開或者需要修改的配置,會將配置文件代碼發出來。
1. JFR 相關 Event
一共4個 Event,但是需要關心的就下面這兩個
- Data Loss:數局丟失 Event,當有數據發生丟失時,會有這個Event 進行記錄。包括開始時間,Amount(本次丟失多少事件),Total(一共丟失多少事件)
- Recording Setting:記錄詳細配置採集 Event,會在每次產生新的 Data Chunk 的時候採集一次所有的 Event 的詳細配置並記錄到這個 Event 中。
這些在 default.jfc 中默認打開
2. JAVA 應用相關
2.1. TLAB 相關
衆所周知,TLAB (Thread Local Allocation Buffer)目的是爲類進行內存快速分配。堆內存所有線程共享訪問,所以在堆內存上面分配對象,就會鎖定整個堆,這樣效率太低。TLAB 是位於堆內存上面的一塊內存區域,在爲每個線程分配 TLAB 的時候纔會鎖定堆(G1 是CAS分配)。分配對象的時候,優先從線程的 TLAB 上分配,這樣就不用和其他線程同步。當對象比較大的時候,例如對於 G1 來說, HeapRegionSize 配置大小的一半以上的對象就被認爲是大對象,大對象的分配不會發生在 TLAB,不在 TLAB 發生的對象分配會涉及到線程同步。
這是比較籠統的看法,針對於 G1,這個算法更加複雜。爲了能說明 JFR 相關事件的意義,這裏繼續深入一下關於 G1 TLAB 相關原理。
創建一個對象時:
- 首先嚐試從線程現有的TLAB空間分配內存
- 如果剩餘空間不足,查看是否能分配一個新的TLAB,再分配內存給對象
- TLAB 的實現內部,每個線程維護一個 refill_waste 的變量,根據這個變量的值決定是否能分配一個新的TLAB。這個變量會根據一定算法隨着線程的運行不斷變化
- 同時, 每個線程的 TLAB 大小也是隨着線程運行不斷變化的
- 當 TLAB 剩餘空間不足時,查看當前 TLAB 的剩餘大小,如果小於 refill_waste 當前值,則認爲 TLAB 該擴容了,需要分配一個新的TLAB,這時候,JFR 會產生一條 ObjectAllocationInNewTLAB Event 記錄;如果不小於,則認爲這個 TLAB 還不算滿,當前這個對象直接走堆上內存分配,不從 TLAB 分配,這時會產生一條 ObjectAllocationOutsideTLAB Event 記錄。
涉及的 Event 以及默認配置:
- ObjectAllocationInNewTLAB: TLAB 擴容時產生的 Event
- 在 default.jfc 中默認沒有打開,可以通過嚮導配置 memory-profiling 調爲 memory-profiling-enabled-medium 打開
- 也可以用高級配置配置這個 Event 是否採集,以及堆棧是否採集
-採集內容包括:時間,線程,本次需要分配內存大小,對象類型,當前 TLAB 大小
- ObjectAllocationOutsideTLAB:
- 在 default.jfc 中默認沒有打開,可以通過嚮導配置 memory-profiling 調爲 memory-profiling-enabled-medium 打開
- 也可以用高級配置配置這個 Event 是否採集,以及堆棧是否採集
- 採集內容包括:時間,線程,本次需要分配內存大小,對象類型
這兩個的採集,對性能影響比較大,不能長期跑。尤其是在啓用堆棧收集後,影響就更大了。一般考慮動態打開。
一般應用:
- 對於上一節裏面需要確定 HeapRegionSize 大小的時候,可以考慮採集一段時間內的 ObjectAllocationOutsideTLAB Event,查看最大需要的內存大小是多少。
- 如果考慮通過減少內存分配,來減少 GC,或者定位大內存分配代碼位置,可以打開這兩個 Event 的採集,查看造成這些事件的熱點堆棧是哪裏,以此優化代碼。
配置打開示例:
<event name="jdk.ObjectAllocationInNewTLAB">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
</event>
<event name="jdk.ObjectAllocationOutsideTLAB">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
</event>
事件 jmc 查看示例:
2.2. 文件操作相關
主要涉及三個 Event:
- FileForce:強制寫的時候,會產生這個 Event
- FileRead:文件讀的時候,會產生這個 Event
- FileWrite:文件寫的時候,會產生這個 Event
以 FileChannel
舉例:
try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "rw");
FileChannel channel = reader.getChannel();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int bufferSize = 1024;
ByteBuffer buff = ByteBuffer.allocate(bufferSize);
channel.read(buff); //產生 FileRead
channel.write(buff); // 產生 FileWrite
channel.force(true); // 強制不寫入高速緩存,直接寫入磁盤文件
channel.write(buff); // 產生 FileForce
}
這三個事件配置可以採集堆棧,設置採集時間閾值。在 default.jfc 中,這三個事件默認都是採集的,堆棧採集打開,並且閾值是20ms。如果你的應用只是打日誌用到了文件,那個這個默認配置就很足夠了。尤其是對於 Log4j2 的異步日誌,這個閾值是夠用的。如果你的應用需要高頻操作文件,例如 RocketMQ 的 日誌文件(基於 mmap)的,則這個閾值最好改成10ms,因爲對文件寫入讀取速度要求更高。
2.3. 異常與錯誤相關
主要涉及兩個 Event:
- Java Error Event:當有 Error 被 throw 時,會產生這個event, default.jfc 中默認開啓這個採集,並且包括堆棧(配置項是 JavaErrorThrow)
- Java Exception Event:當有 Exception 被 throw 時,會產生這個event,default.jfc 中默認不開啓這個採集,並且包括堆棧(配置項是 JavaExceptionThrow)
我建議不用開啓這兩個 EVent 的採集,因爲 Exception 我們可以通過日誌分析, Error 一般框架都會有一些,一般與爲我們的業務無關。