今天,又是乾貨滿滿的一天。這是全網最硬核 JVM 系列的開篇,首先從 TLAB 開始。因爲文章很長,每一個人閱讀習慣不一樣,因此特此拆成單篇版和多篇版java
- 全網最硬核 JVM TLAB 分析(單篇版不包含額外加菜)
- 全網最硬核 JVM TLAB 分析 1. 內存分配思想引入
- 全網最硬核 JVM TLAB 分析 2. TLAB生命週期與帶來的問題思考
- 全網最硬核 JVM TLAB 分析 3. JVM EMA指望算法與TLAB相關JVM啓動參數
- 全網最硬核 JVM TLAB 分析 4. TLAB 基本流程全分析
- 全網最硬核 JVM TLAB 分析 5. TLAB 源代碼全解析
- 全網最硬核 JVM TLAB 分析 6. TLAB 相關熱門Q&A彙總
- 全網最硬核 JVM TLAB 分析(額外加菜) 7. TLAB 相關 JVM 日誌解析
- 全網最硬核 JVM TLAB 分析(額外加菜) 8. 經過 JFR 監控 TLAB
咱們能夠經過 JFR 來監控 TLAB 慢分配或者 TLAB 外分配事件。也就是jdk.ObjectAllocationOutsideTLAB
與jdk.ObjectAllocationInNewTLAB
這兩個事件。git
jdk.ObjectAllocationOutsideTLAB
和 jdk.ObjectAllocationInNewTLAB
這兩個事件在default.jfc
中( JFR 默認事件採集配置)是沒有開啓採集的:github
<event name="jdk.ObjectAllocationInNewTLAB">
<setting name="enabled">false</setting>
<setting name="stackTrace">true</setting>
</event>
<event name="jdk.ObjectAllocationOutsideTLAB">
<setting name="enabled">false</setting>
<setting name="stackTrace">true</setting>
</event>
複製代碼
通常的,採集這兩個事件,是須要連着堆棧一塊兒採集,可是沒法經過持續時間(由於這個事件沒有持續時間這一律念)限制採集哪些,也就是隻要開啓就是所有采集,因此不建議長期開啓這個採集。而是經過一些其餘的監控項,按照須要,動態開啓這個採集一段時間,以後關閉並 dump 出 JFR 文件用於分析。算法
那麼通常根據什麼指標判斷呢?通常的,當 Young GC 過於頻繁時,咱們就要考慮是否是因爲 TLAB 形成不少空間被浪費致使 GC 頻繁了。至於若是採集 Young GC 頻率從而動態開啓,這個會在後面的動態監控章節詳細說明。bootstrap
咱們還用上面的程序,根據以前的日誌,對於 1KB 的對象,應該有兩次 jdk.ObjectAllocationInNewTLAB
事件,第一次是線程第一次申請 TLAB,第二次是在分配第 512 個對象的時候,TLAB 剩餘空間不足,同時剩餘空間小於最大浪費空間限制,因此申請新的 TLAB 分配。對於 1KB 的分配,沒有發生 jdk.ObjectAllocationOutsideTLAB
。對於 100KB 的對象分配,在第五次分配時,TLAB 剩餘空間不足,可是剩餘空間大於最大浪費空間限制,直接在 Eden 區分配,同時將最大浪費空間限制增長 4。在第 114 次對象分配時,最大浪費空間限制達到了剩餘空間,因此申請新的 TLAB 分配。因此對於 100KB 對象的 200 次分配裏面,jdk.ObjectAllocationInNewTLAB
也只有兩次。數組
同時因爲開啓了 JFR,致使 TLAB 可能會被佔用一部分,因此上面說的這些次數可能不太準確,不過不要緊,大致上應該是對的。markdown
//對於字節數組對象頭佔用16字節
private static final int BYTE_ARRAY_OVERHEAD = 16;
//咱們要測試的對象大小是100kb
private static final int OBJECT_SIZE = 100 * 1024;
//須要使用靜態field,而不是方法內本地變量,不然編譯後循環內的new byte[]所有會被省略,只剩最後一次的
public static byte[] tmp;
public static void main(String[] args) throws Exception {
WhiteBox whiteBox = WhiteBox.getWhiteBox();
//初始化 JFR 記錄
Recording recording = new Recording();
//啓用 jdk.ObjectAllocationOutsideTLAB 事件監控
recording.enable("jdk.ObjectAllocationOutsideTLAB");
recording.enable("jdk.ObjectAllocationInNewTLAB");
// JFR 記錄啓動
recording.start();
//強制 fullGC 防止接下來程序發生 GC
//同時能夠區分出初始化帶來的其餘線程的TLAB相關的日誌
whiteBox.fullGC();
//分配對象,大小1KB
for (int i = 0; i < 512; ++i) {
tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
}
//強制 fullGC,回收全部 TLAB
whiteBox.fullGC();
//分配對象,大小100KB
for (int i = 0; i < 200; ++i) {
tmp = new byte[OBJECT_SIZE * 100 - BYTE_ARRAY_OVERHEAD];
}
whiteBox.fullGC();
//將 JFR 記錄 dump 到一個文件
Path path = new File(new File(".").getAbsolutePath(), "recording-" + recording.getId() + "-pid" + ProcessHandle.current().pid() + ".jfr").toPath();
recording.dump(path);
int countOf1KBObjectAllocationInNewTLAB = 0;
int countOf100KBObjectAllocationInNewTLAB = 0;
int countOf1KBObjectAllocationOutsideTLAB = 0;
int countOf100KBObjectAllocationOutsideTLAB = 0;
//讀取文件中的全部 JFR 事件
for (RecordedEvent event : RecordingFile.readAllEvents(path)) {
//獲取分配的對象的類型
String className = event.getString("objectClass.name");
if (
//確保分配類型是 byte[]
BYTE_ARRAY_CLASS_NAME.equalsIgnoreCase(className)
) {
RecordedFrame recordedFrame = event.getStackTrace().getFrames().get(0);
//同時必須是我們這裏的main方法分配的對象,而且是Java堆棧中的main方法
if (recordedFrame.isJavaFrame()
&& "main".equalsIgnoreCase(recordedFrame.getMethod().getName())
) {
//獲取分配對象大小
long allocationSize = event.getLong("allocationSize");
//統計各類事件個數
if ("jdk.ObjectAllocationOutsideTLAB".equalsIgnoreCase(event.getEventType().getName())) {
if (allocationSize == 102400) {
countOf100KBObjectAllocationOutsideTLAB++;
} else if (allocationSize == 1024) {
countOf1KBObjectAllocationOutsideTLAB++;
}
} else if ("jdk.ObjectAllocationInNewTLAB".equalsIgnoreCase(event.getEventType().getName())) {
if (allocationSize == 102400) {
countOf100KBObjectAllocationInNewTLAB++;
} else if (allocationSize == 1024) {
countOf1KBObjectAllocationInNewTLAB++;
}
} else {
throw new Exception("unexpected size of TLAB event");
}
System.out.println(event);
}
}
}
System.out.println("countOf1KBObjectAllocationInNewTLAB: " + countOf1KBObjectAllocationInNewTLAB);
System.out.println("countOf100KBObjectAllocationInNewTLAB: " + countOf100KBObjectAllocationInNewTLAB);
System.out.println("countOf1KBObjectAllocationOutsideTLAB: " + countOf1KBObjectAllocationOutsideTLAB);
System.out.println("countOf100KBObjectAllocationOutsideTLAB: " + countOf100KBObjectAllocationOutsideTLAB);
//阻塞程序,保證全部日誌輸出完
Thread.currentThread().join();
}
複製代碼
輸出應該近似於:ide
//省略其餘事件的詳細信息,這裏每種挑一個展現
jdk.ObjectAllocationInNewTLAB {
startTime = 13:07:51.681
objectClass = byte[] (classLoader = bootstrap)
allocationSize = 1.0 kB
tlabSize = 478.2 kB
eventThread = "main" (javaThreadId = 1)
stackTrace = [
com.github.hashjang.jfr.test.TestAllocOutsideTLAB.main(String[]) line: 96
]
}
jdk.ObjectAllocationInNewTLAB {
startTime = 13:07:51.777
objectClass = byte[] (classLoader = bootstrap)
allocationSize = 100.0 kB
tlabSize = 512.0 kB
eventThread = "main" (javaThreadId = 1)
stackTrace = [
com.github.hashjang.jfr.test.TestAllocOutsideTLAB.main(String[]) line: 102
]
}
jdk.ObjectAllocationOutsideTLAB {
startTime = 13:07:51.784
objectClass = byte[] (classLoader = bootstrap)
allocationSize = 100.0 kB
eventThread = "main" (javaThreadId = 1)
stackTrace = [
com.github.hashjang.jfr.test.TestAllocOutsideTLAB.main(String[]) line: 102
]
}
//省略其餘事件的詳細信息,這裏每種挑一個展現
countOf1KBObjectAllocationInNewTLAB: 2
countOf100KBObjectAllocationInNewTLAB: 2
countOf1KBObjectAllocationOutsideTLAB: 0
countOf100KBObjectAllocationOutsideTLAB: 190
複製代碼
能夠看出jdk.ObjectAllocationInNewTLAB
包含:oop
startTime = 13:07:51.784
objectClass = byte[] (classLoader = bootstrap)
allocationSize = 100.0 kB
tlabSize = 512.0 kB
eventThread = "main" (javaThreadId = 1)
jdk.ObjectAllocationOutsideTLAB
包含:post
startTime = 13:07:51.784
objectClass = byte[] (classLoader = bootstrap)
allocationSize = 100.0 kB
eventThread = "main" (javaThreadId = 1)
咱們通常經過 JMC 查看 JFR 監控的文件,經過事件查看器就能夠查看其中的事件,能夠參考個人另外一系列:JFR 全解