問題產(chǎn)生于思考Tomcat與JVM的關(guān)系械姻,發(fā)現(xiàn)自己就是個孩子,對它們一無所知。
Tomcat是一個用Java語言寫出來的應(yīng)用程序官紫,所以每運行一個Tomcat實例必將開啟一個JVM進程。啟動多個Tomcat將會產(chǎn)生多個JVM進程州藕,每個JVM進程中可以部署運行多個Web應(yīng)用程序束世,即可以存在多個類加載器(見下文)。經(jīng)過對基本概念的重新梳理床玻,簡單表示如下圖毁涉。
類加載器
public class LookForClassLoader {
public static void main(String[] args) {
ClassLoader currentLoader = Thread.currentThread().getContextClassLoader(); // 獲取當(dāng)前線程上下文的類加載器
System.out.println("current classLoader:" + currentLoader.getClass());
System.out.println("parent classLoader:" + currentLoader.getParent().getClass());
System.out.println("grandparent classLoader:" + currentLoader.getParent().getParent());
}
}
Output:
current classLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
當(dāng)前AppClassLoader實例
parent classLoader:class sun.misc.Launcher\$ExtClassLoader@4554617c
父類ExtClassLoader實例
grandparent classLoader:null
祖類BootstrapClassLoader實例
祖類BootstrapClassLoader實例為null,是由C++所寫直接嵌入在 JVM 內(nèi)核中笨枯,在Java中無法獲得它的句柄薪丁,所以直接返回null。除了Java默認(rèn)提供的三個ClassLoader之外馅精,用戶可以根據(jù)需要定義自己的ClassLoader严嗜,這些自定義的ClassLoader都必須繼承自java.lang.ClassLoader類,但是Bootstrap ClassLoader不繼承自ClassLoader洲敢。當(dāng)JVM啟動后漫玄,Bootstrap ClassLoader也隨著啟動,負(fù)責(zé)加載完核心類庫后,并構(gòu)造Extension ClassLoader和App ClassLoader類加載器睦优。
上圖展示的類加載器之間的這種層次關(guān)系渗常,稱為類加載器的雙親委派模型。雙親委派模型要求除了頂層的啟動類加載器外汗盘,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器皱碘。這里類加載器之間的父子關(guān)系一般不會以繼承的關(guān)系來實現(xiàn),而是使用組合/包含關(guān)系來復(fù)用父加載器的代碼隐孽。
加載類:自頂向下
當(dāng)一個ClassLoader實例需要加載某個類時癌椿,在它親自搜索之前,會先把這個任務(wù)委托給它的父類加載器菱阵,這個過程是由上至下依次檢查的踢俄,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒加載到晴及,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載都办,如果也沒加載到,則轉(zhuǎn)交給App ClassLoader 進行加載虑稼,如果它也沒有加載得到的話琳钉,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類动雹。如果它們都沒有加載到這個類時槽卫,則拋出ClassNotFoundException異常。否則將這個找到的類生成一個類的定義胰蝠,并將它加載到內(nèi)存當(dāng)中歼培,最后返回這個類在內(nèi)存中的Class實例對象。
詢問是否已加載類:自底向上
可以避免重復(fù)加載茸塞,當(dāng)父親已經(jīng)加載了該類的時候躲庄,就沒有必要子ClassLoader再加載一次。同時考慮到安全因素钾虐,我們試想一下噪窘,如果不使用這種委托模式,那我們就可以隨時使用自定義的String來動態(tài)替代java核心api中定義的類型效扫,這樣會存在非常大的安全隱患倔监,而雙親委托的方式,就可以避免這種情況菌仁,因為String已經(jīng)在啟動時就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載浩习,所以用戶自定義的ClassLoader永遠(yuǎn)也無法加載一個自己寫的String,除非改變JDK中ClassLoader搜索類的默認(rèn)算法济丘。
JVM在判定兩個class是否相同時谱秽,不僅要判斷兩個類名是否相同洽蛀,而且要判斷是否由同一個類加載器實例加載的。只有兩者同時滿足的情況下疟赊,JVM才認(rèn)為這兩個class是相同的郊供。就算兩個class是同一份class字節(jié)碼,如果被兩個不同的ClassLoader實例所加載近哟,JVM也會認(rèn)為它們是兩個不同class驮审。
BootStrap classLoader加載的類
System.out.println(System.getProperty("sun.boot.class.path")); // BootStrap classLoader加載的類
Output:
D:\Java\jdk1.8.0_74\jre\lib\resources.jar;
// 資源包(圖片、properties文件)
D:\Java\jdk1.8.0_74\jre\lib\rt.jar;
// Bootstrap類椅挣,引導(dǎo)類(構(gòu)成Java平臺核心API的運行時類)
D:\Java\jdk1.8.0_74\jre\lib\sunrsasign.jar;
D:\Java\jdk1.8.0_74\jre\lib\jsse.jar;
// Java 安全套接字?jǐn)U展類庫头岔,用于實現(xiàn)加密的 Socket 連接
D:\Java\jdk1.8.0_74\jre\lib\jce.jar;
// Java 加密擴展類庫,含有很多非對稱加密算法在里面鼠证,也是可擴展的
D:\Java\jdk1.8.0_74\jre\lib\charsets.jar;
// Java 字符集,這個類庫中包含 Java 所有支持字符集的字符
D:\Java\jdk1.8.0_74\jre\lib\jfr.jar;
// Java飛行記錄器 Flight Recorder靠抑,可以深入分析問題量九,使用參考
D:\Java\jdk1.8.0_74\jre\classes
java -verbose[:class|gc|jni]
-
java -verbose:class
輸出虛擬機裝入的類的信息,顯示的信息格式如下
[Opened D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.lang.Object from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.io.Serializable from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.lang.Comparable from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.util.Arrays from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.nio.charset.Charset$ExtendedProviderHolder from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.nio.charset.Charset$ExtendedProviderHolder$1 from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
...
[Loaded java.lang.Class$MethodArray from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.lang.Void from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
current classLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
parent classLoader:sun.misc.Launcher$ExtClassLoader@4554617c
grandparent classLoader:null
[Loaded java.lang.Shutdown from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from D:\Java\jdk1.8.0_74\jre\lib\rt.jar]
-
java -verbose:gc
監(jiān)視虛擬機內(nèi)存回收的情況颂碧,格式如輸出所示
public static void showGCInfo(){
LookForClassLoader obj = new LookForClassLoader();
System.gc();
}
Output:
[GC (System.gc()) 2663K->672K(125952K), 0.0082196 secs]
[Full GC (System.gc()) 672K->569K(125952K), 0.0158595 secs]
箭頭前后的數(shù)據(jù)672K和569K分別表示垃圾收集GC前后所有存活對象使用的內(nèi)存容量荠列,說明有672K-569K=103K的對象容量被回收,括號內(nèi)的數(shù)據(jù)125952K為堆內(nèi)存的總?cè)萘吭爻牵占枰臅r間是0.0158595 秒(這個時間在每次執(zhí)行的時候會有所不同)
在VM的啟動參數(shù)中加入-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime肌似,分別輸出GC的簡要信息,GC的詳細(xì)信息诉瓦、GC的時間信息及GC造成的應(yīng)用暫停的時間川队。
-
java -verbose:jni
輸出native方法調(diào)用的情況,一般用于診斷jni調(diào)用錯誤信息睬澡,格式如下:
[Dynamic-linking native method java.lang.Object.registerNatives ... JNI]
[Registering JNI native method java.lang.Object.hashCode]
[Registering JNI native method java.lang.Object.wait]
[Registering JNI native method java.lang.Object.notify]
[Registering JNI native method java.lang.Object.notifyAll]
[Registering JNI native method java.lang.Object.clone]
[Dynamic-linking native method java.lang.System.registerNatives ... JNI]
[Registering JNI native method java.lang.System.currentTimeMillis]
[Registering JNI native method java.lang.System.nanoTime]
[Registering JNI native method java.lang.System.arraycopy]
[Dynamic-linking native method java.lang.Thread.registerNatives ... JNI]
[Registering JNI native method java.lang.Thread.start0]
[Registering JNI native method java.lang.Thread.stop0]
[Registering JNI native method java.lang.Thread.isAlive]
[Registering JNI native method java.lang.Thread.suspend0]
[Registering JNI native method java.lang.Thread.resume0]
...
“貓”的后續(xù)
JVM 的后續(xù)
參考博客:
https://www.cnblogs.com/jingmoxukong/p/8258837.html?utm_source=gold_browser_extension
https://my.oschina.net/zhengjian/blog/133836
https://www.cnblogs.com/z00377750/p/9167768.html