虛擬機的類加載機制
這篇文章是學習《深入理解JAVA虛擬機》中的一些個人認爲比較重要的知識點的總結
關於類的初始化
類的生命週期: 加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用和卸載(Using and Unloading)
-
加載:獲取類的二進制字節流
-
驗證:確保Class文件的字節流中包含的信息符合當前虛擬機的要求
-
準備:爲類變量分配內存並設置類變量初始值(不包含實例變量)
-
解析:將常量池內的符號引用替換爲直接引用的過程
-
初始化:類加載過程的最後一步,開始執行類中定義的Java程序代碼
-
使用:
-
卸載:
5種觸發類進行初始化的場景:
-
new(使用new初始化一個類)、getStatic\putStatic(獲取或設置一個類的靜態字段)、invokeStatic(調用一個類的靜態方法)
-
使用java.lang.reflect 包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先出發其初始化
-
初始化一個類的時候,如果發現其父類還沒進行過初始化,則需要先觸發其父類的初始化
-
虛擬機啓動時,用戶需要指定一個要執行的(包含main()方法的那個類),虛擬機會先初始化這個主類
-
略
其中一個demo
public class NotInitialization1 {
public static void main(String[] args) {
/**
* 例子一:
* 1. 讀取非final修飾的靜態字段時,如果類未被初始化則去初始化
* 2. 當初始化一個類的時候,如果發現父類還未被初始化,則需要先出發父類的初始化
* 3. 通過子類來引用父類中定義的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化
* @param args
*/
System.out.println(SubClass.value);
}
}
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
運行結果:
SuperClass init!
123
關於類加載器 Classloader
虛擬機設計團隊把類加載階段中“通過一個類的名稱來獲取描述此類的二進制字節流”,放到虛擬機外部去實現。這個代碼模塊成爲“類加載器”。
注意: 即使兩個類來源於同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那麼這兩個類必定不相等。
import java.io.IOException;
import java.io.InputStream;
/**
* 示例:
* 即使這兩個類都來源於同一個Class文件,但是屬於兩個不同的類加載器
* 一個是由系統應用程序類加載器加載的,另一個由自定義的類加載器
*
* 結論:
* 即使兩個類來源於同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那麼這兩個類必定不相等
*
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//自定義一個類加載器
ClassLoader myLoader = new ClassLoader() {
/**
* Loads the class with the specified <a href="#name">binary name</a>.
* This method searches for classes in the same manner as the {@link
* #loadClass(String, boolean)} method. It is invoked by the Java virtual
* machine to resolve class references. Invoking this method is equivalent
* to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
* false)</tt>}.
*
* @param name The <a href="#name">binary name</a> of the class
* @return The resulting <tt>Class</tt> object
* @throws ClassNotFoundException If the class was not found
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[0];
b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
//用自定義的類加載器去加載一個類
Object obj = myLoader.loadClass("java8.classloading.classloader.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
//與相對路徑的類相對比
System.out.println(obj instanceof java8.classloading.classloader.ClassLoaderTest);
}
}
運行結果:
class java8.classloading.classloader.ClassLoaderTest
false
關於雙親委派模型(Parents Delegation Model)
3種類加載器
- 啓動類加載器(Bootstrap ClassLoader)
-
這個類加載器使用C++語言實現,是虛擬機自身的一部分;
-
無法被Java程序直接引用
-
負責把類庫加載到虛擬機內存中
-
放在<JAVA_HOME>\lib目錄中
-
或者被 -Xbootclasspath 參數所指定的路徑中的
-
被虛擬機識別的(按照文件名識別,如rt.jar)
-
- 擴展類加載器(Extension ClassLoader)
-
由sun.misc.Launcher$ExtClassLoader實現
-
開發者可以直接使用
-
負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫
- 應用程序類加載器(Application ClassLoader)
-
由sun.misc.Launcher$App-ClassLoader實現
-
一般也稱爲系統加載器。程序中的默認類加載器
-
加載用戶類路徑(ClassPath)上所指定的類庫
什麼是雙親委派模型
如圖所示:類加載器之間的這種層次關係,稱爲類加載器的雙親委派模型
- 工作流程:
如果一個類加載器收到了類加載的請求,先把請求委派給父類加載器去完成,每層如此;只有父加載器反饋自己無法完成這個加載請求(它的搜索範圍中沒有找到所需的類),子加載器纔會嘗試自己去加載
- 好處:
保證程序裏各類加載器環境中都是同一個類,對於保證Java程序的穩定運作很重要
(代碼集中在java.lang.ClassLoader的loadClass()方法中)
題外
-
熱部署: 其實是通過自定義類加載機制實現了,其破壞了雙親委派模型(OSGI:Java模塊化標準)
-
關於反射: 提供很多方法,可以直接通過(類名.方法)的形式獲取類的相關信息(變量、方法等)