JAVA ClassLoader類載入機制
參考文章:https://javasec.org/javase/ClassLoader/
關於類的載入機制,結果javasec和自己的理解來記錄的一篇文章!!!
概念的學習
首先得知道的兩點:
第一點:
Java是一個依賴於JVM(Java虛擬機器)實現的跨平臺的開發語言
第二點:類例項化的工作流程:
1、Java程式在執行前需要先編譯成class檔案(位元組碼),這個class檔案其實就是我們的javac生成的! 2、Java類初始化的時候會呼叫ClassLoader物件(這裡的ClassLoader其實也是一個類)載入類位元組碼(也就是class檔案) 3、ClassLoader會呼叫JVM的native方法(defineClass0/1/2)來定義一個java.lang.Class例項。
現在知道了兩個概念:
1、JAVA的執行依賴於JAVA虛擬機器
2、類的初始化需要ClassLoader物件
3、想要生成對應的類例項就需要ClassLoader去載入對應類的位元組碼(也就是class檔案)
注意:這裡說的類例項,並不是如Unsafe unsafe = new Unsafe()
這裡面生成的unsafe例項,此unsafe例項跟這裡的類例項不同的,為什麼?
就比如這一句話:ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
,看著意思就是一個ClassLoader類中的getSystemClassLoader方法返回了一個物件然後呼叫該物件的loadClass方法載入了一個名為java.lang.String類,那麼這裡其實就知道了主要載入類的方法就是loadClass
可以觀察下該loadClass返回的是什麼,如下,那麼也就知道了這是一個名叫Class型別的物件,那麼也就是返回的是一個Class型別的物件,那麼也就證實了上面說的,unsafe例項跟這裡的類例項不同!
ClassLoader的介紹
觀察如下一張圖:
1、可以看到ClassLoader是位於JVM裡面的
2、所有的類的例項化都需要經過ClassLoader
總結:上面的圖中可以看到一切的Java類都必須經過JVM載入後才能執行,而ClassLoader的主要作用就是Java類的載入!
底層小知識:
1、在JVM類載入器中最頂層的是Bootstrap ClassLoader(引導類載入器)、Extension ClassLoader(擴充套件類載入器)、App ClassLoader(系統類載入器),App ClassLoader是預設的類載入器,如果類載入時我們不指定類載入器的情況下,預設會使用App ClassLoader載入類,ClassLoader.getSystemClassLoader()返回的系統類載入器就是AppClassLoader
然後繼續說,上面說了ClassLoader物件並不是只有一種,現在知道的有如上三種,其他的就是我們重寫的ClassLoader類了
Java自帶的三個類載入器:
Bootstrap Classloder
Extention ClassLoader
App ClassLoader
上一級稱為下一級的父載入器(怎麼理解,也就是Extention ClassLoader是App ClassLoader的父載入器),載入的先後順序依次是:Bootstrap ClassLoader => Extention ClassLoader => App ClassLoader
圖中可以知道:檢查類載入和載入類生成的時候順序是相反的,如圖中所示,當一個類載入器查詢class和resource的時候,首先判斷這個class是不是已經載入成功;如果沒有的話它並不是自己進行查詢,而是先委託給父載入器,然後遞迴委託,直到Bootstrap ClassLoader載入器;如果Bootstrap classloader找到了,直接載入並且返回class和resource;如果沒有找到,則一級一級返回,最後才是自己(這裡的自己正常的肯定就是App ClassLoader,要不然就是你的自定義的類載入器)去查詢載入。
需要知道的:某些時候我們獲取一個類的類載入器時候可能會返回一個null值,如:java.io.File.class.getClassLoader()將返回一個null物件,因為java.io.File類在JVM初始化的時候會被Bootstrap ClassLoader(引導類載入器)載入(該類載入器實現於JVM層,採用C++編寫),我們在嘗試獲取被Bootstrap ClassLoader類載入器所載入的類的ClassLoader時候都會返回null
ClassLoader類的方法
ClassLoader最重要的如下5個方法:
1. loadClass (載入指定的Java類)
2. findClass (查詢指定的Java類)
3. findLoadedClass (查詢JVM已經載入過的類)
4. defineClass (定義一個Java類)
5. resolveClass (連結指定的Java類)
第一個方法,loadClass,一開始在開頭也稍微介紹了下,該方法的作用是載入指定的Java類,跟一下這個方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
這裡呼叫了一個loadClass,兩個引數的方法,繼續跟
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name); //這裡先進行判斷要載入的類名對應的類例項是否已經載入過了,這裡findLoadedClass的作用其實就是 如果該類例項已經載入過了,那麼它後續的操作就會直接返回
if (c == null) { //如果為空,那麼也就是沒有載入
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false); // 這裡判斷 父載入器是否為存在,如果存在就讓父類載入器去載入
} else {
c = findBootstrapClassOrNull(name); //如果例項化ClassLoader的時候傳入了父類載入器(new ClassLoader(你傳入的父類載入器))就使用父類載入器載入TestHelloWorld類,否則使用JVM的Bootstrap ClassLoader載入。
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) { // 這裡的就是根本找不到類
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}