JVM 參數(shù):跟蹤類的加載與卸載
1. 前言
本節(jié)內(nèi)容主要是學(xué)習(xí), JVM 跟蹤類的加載與卸載的常用參數(shù)配置,這是工作中跟蹤類的加載與卸載情況時(shí) JVM 中最常用的參數(shù)配置。本節(jié)主要知識(shí)點(diǎn)如下:
- 理解并掌握跟蹤類的加載與卸載的參數(shù) -XX:+TraceClassLoading,為本節(jié)重點(diǎn)內(nèi)容;
- 理解并掌握跟蹤類的加載與卸載的參數(shù) -XX:+TraceClassUnloading,為本節(jié)了解內(nèi)容,非重點(diǎn)知識(shí);
- 理解并掌握跟蹤類的加載與卸載的參數(shù) -XX:+PrintClassHistogram,為本節(jié)重點(diǎn)內(nèi)容;
JVM 跟蹤類的加載與卸載的常用參數(shù)是使用 JVM 所必須的知識(shí)點(diǎn),通篇皆為重點(diǎn)掌握內(nèi)容,需要在理解的基礎(chǔ)上并掌握參數(shù)的使用方法。
2. 示例代碼準(zhǔn)備
此處的示例代碼,與上一節(jié)的示例代碼相似,但是有重要的區(qū)別。詳細(xì)區(qū)別請(qǐng)看 Tips 的內(nèi)容。
實(shí)例:準(zhǔn)備測(cè)試代碼,創(chuàng)建一個(gè) String 類型的 ArrayList,并在 list 中添加三個(gè)元素,分別是 “Hello”,“World”,“?。?!”。
Tips:注意,此處的示例代碼,并沒有執(zhí)行 gc 操作。上一節(jié)的內(nèi)容是為了跟蹤垃圾回收,所以需要手動(dòng)調(diào)用 gc 方法而達(dá)到垃圾回收的效果。而此處我們討論的是類的加載與卸載,此處無需進(jìn)行手動(dòng)垃圾回收。
public class TracingClassParamsDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
list.add("!!!");
}
}
3. -XX:+TraceClassLoading 參數(shù)
參數(shù)作用:-XX:+TraceClassLoading 參數(shù)是為了跟蹤類的加載。
為了更好的理解并掌握 -XX:+TraceClassLoading 參數(shù),我們通過如下步驟進(jìn)行操作。
- 步驟 1:在 VM Options 中配置參數(shù) -XX:+TraceClassLoading 并保存;
- 步驟 2:運(yùn)行示例代碼,觀察執(zhí)行結(jié)果。
結(jié)果驗(yàn)證:由于追蹤的結(jié)果日志非常龐大,此處僅展示具有代表性的類的加載。全部的類加載日志,請(qǐng)學(xué)習(xí)者自行執(zhí)行代碼進(jìn)行驗(yàn)證。
[Opened C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded java.lang.Object from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded java.util.ArrayList$SubList from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded java.util.ListIterator from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded java.util.ArrayList$SubList$1 from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded DemoMain.TracingClassParamsDemo from file:/D:/GIT-Repositories/GitLab/Demo/out/production/Demo/]
[Loaded java.lang.Class$MethodArray from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded java.lang.Void from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded java.lang.Shutdown from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar]
結(jié)果分析:我們來對(duì)類的加載日志進(jìn)行分析。
- 第一行:Opened rt.jar。打開 rt.jar,rt.jar 全稱是 Runtime,該 jar 包含了所有支持 Java 運(yùn)行的核心類庫,是類加載的第一步;
- 第二行:加載 java.lang.Object。Object 是所有對(duì)象的父類,是首要加載的類;
- 第三、四、五行:加載了 ArrayList 的相關(guān)類,我們的示例代碼中使用到了 ArrayList,因此需要對(duì)該類進(jìn)行加載;
- 第六行:加載我們的測(cè)試類 TracingClassParamsDemo ;
- 第七行:加載 java.lang.Class 類,并加載類方法 MethodArray;
- 第八行:加載 java.lang.Void 類,因?yàn)槲覀兊?main 函數(shù)是 void 的返回值類型,所以需要加載此類;
- 第九、十行:加載 java.lang.Shutdown 類, JVM 結(jié)束運(yùn)行后,關(guān)閉 JVM 虛擬機(jī)。
從以上對(duì)日志的分析來看,JVM 對(duì)類的加載,不僅僅加載我們代碼中使用的類,還需要加載各種支持 Java 運(yùn)行的核心類。類加載的日志量非常龐大,此處僅僅對(duì)重點(diǎn)類的加載進(jìn)行日志的解讀,全部的類加載日志,請(qǐng)學(xué)習(xí)者自行執(zhí)行代碼進(jìn)行驗(yàn)證。
4. -XX:+TraceClassUnloading 參數(shù)
參數(shù)作用:-XX:+TraceClassUnloading 參數(shù)是為了跟蹤類的卸載。由于系統(tǒng)類加載器加載的類不會(huì)被卸載,并且只加載一次,所以普通項(xiàng)目很難獲取到類卸載的日志。
此處我們先來看看,通過系統(tǒng)類加載器加載的類是否會(huì)被卸載。
為了更好的理解并掌握 -XX:+TraceClassUnloading 參數(shù),我們通過如下步驟進(jìn)行操作。
- 步驟 1:在 VM Options 中配置參數(shù) -XX:+TraceClassUnloading 并保存;
- 步驟 2:運(yùn)行示例代碼,觀察執(zhí)行結(jié)果。
結(jié)果驗(yàn)證:未打印日志,未發(fā)生類的卸載。
引出問題:為什么看不到跟蹤類卸載的日志呢?
上文提到了,由系統(tǒng)類加載器加載的類不能夠被卸載。所以想要看到跟蹤類卸載的日志,我們需要使用自定義的類加載器。通過自定義的類加載器加載的類,在類不可達(dá)的時(shí)候,會(huì)發(fā)生垃圾回收,并卸載該類。
一般情況下,開發(fā)過程中很少實(shí)現(xiàn)自定義的類加載器,除非有特殊的需求場景需要通過自定義的類加載器進(jìn)行類的加載,因此此處對(duì) -XX:+TraceClassUnloading 稍作了解即可。
5. -XX:+PrintClassHistogram 參數(shù)
參數(shù)作用:-XX:+PrintClassHistogram 參數(shù)是打印、查看系統(tǒng)中類的分布情況。
為了更好的理解并掌握 -XX:+PrintClassHistogram 參數(shù),我們通過如下步驟進(jìn)行操作。
- 步驟 1:在 VM Options 中配置參數(shù) -XX:+PrintClassHistogram 并保存;
- 步驟 2:修改示例代碼,在代碼最后添加代碼
Thread.sleep(99999999999L)
,確保 main 函數(shù)長時(shí)間不能結(jié)束執(zhí)行(當(dāng)然了,也可以使用 while (true) 語句,進(jìn)行無限長時(shí)間循環(huán)來創(chuàng)造這種場景,可自行選擇),以便于觀察類的分布情況;
public class TracingClassParamsDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
list.add("!!!");
try {
Thread.sleep(99999999999L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 步驟 3:運(yùn)行程序,觀察日志輸出;
- 步驟 4:不中斷 main 函數(shù)的運(yùn)行,將鼠標(biāo)指針移動(dòng)到日志輸出的 console 界面并單擊鼠標(biāo)左鍵,確保鼠標(biāo)的實(shí)時(shí)位置在 console 界面。按下鍵盤 Ctrl+Break 鍵,觀察日志輸出。
結(jié)果驗(yàn)證:我們執(zhí)行步驟 3 時(shí),沒有觀察到日志的輸出。當(dāng)我們嘗試步驟 4 時(shí),獲取到了日志輸出如下圖所示。
結(jié)果分析: 這是系統(tǒng)中類的分布情況,那我們來看下日志中每列的表頭部分代表的意思:
- num:自增的序列號(hào),只是為了標(biāo)注行數(shù),沒有特殊的意義;
- instances:實(shí)例數(shù)量,即類的數(shù)量;
- bytes:實(shí)例所占子節(jié)數(shù),即占據(jù)的內(nèi)存空間大小;
- class name:具體的實(shí)例。
我們?nèi)〕龅?3 條日志來進(jìn)行下翻譯:系統(tǒng)當(dāng)前狀態(tài)下,java.lang.String 類型的實(shí)例共有 2700 個(gè),共占用空間大小為 64800 bytes。
6. 小結(jié)
本小節(jié)的重點(diǎn)內(nèi)容即,我們所講述的三個(gè)常用的跟蹤類的加載與卸載參數(shù),學(xué)習(xí)者需要對(duì)這三個(gè)常用參數(shù)的意義,以及使用方式進(jìn)行掌握。
需要特別注意第二個(gè)參數(shù) -XX:+TraceClassUnloading,在后續(xù)講解類加載器的時(shí)候,會(huì)實(shí)現(xiàn)自定義的類加載器,并使用該參數(shù)演示類的卸載。通篇皆為重點(diǎn)內(nèi)容,需要認(rèn)真對(duì)待,掌握本節(jié)要點(diǎn)內(nèi)容。