深入Java虛擬機器筆記---ClassLoader
一,ClassLoader 概念
Java中例項化一個類等操作之前需要把類裝入虛擬機器中。這個國政就是類載入,即載入,連線,初始化。其中的載入過程就是由類載入器 ClassLoader 完成的。
基本上所有的類載入器都是java.lang.ClassLoader 類的一個例項。
二,ClassLoader分類
Java中的類載入器可以分成兩類
- 系統提供的
- 開發人員自己編寫
系統提供的類載入器主要由三個:
- 啟動類載入器(Bootstrap ClassLoader)
- 擴充套件類載入器(Extensions ClassLoader),負責載入Java的擴充套件類庫,預設載入JAVA_HOME/jre/lib/ext/目下的所有jar
- 系統類載入器(System ClassLoader),負責載入應用程式classpath目錄下的所有jar和class檔案。
盜用這裡的各種類載入器之間的關係圖:
除了系統提供的類載入器之外,開發人員可以通過繼承java.lang.ClassLoader類來自己實現類載入器。
上圖可以看到除了啟動類載入器其他載入器都有自己的父類。
三,雙親委派機制
如何判定兩個Java類相同
Java 虛擬機器判定兩個類是否相同,不僅要看類的全名,還要看載入此類的類載入器是否相同。只有兩者都相同才會認為相同。
如果試圖在兩個類物件之間進行賦值操作,會丟擲java.lang.ClassCastException。這個特性為同樣名稱的Java類在JVM中共同存在建立了條件。
什麼是雙親委派機制
如果一個類載入器收到了類載入的請求,首先不會自己去嘗試載入這個類,而是把請求委派給父類載入器去完成。一次類推,所有的類載入請求都會傳遞給啟動類載入器。父類載入器嘗試在對應的類路徑下尋找class 位元組碼檔案並載入,如果載入失敗,子類才會嘗試自己去載入。
雙親委派機制是為了保證Java核心庫的型別安全。這種模型保證了Java 類隨著它的父類載入器一起具備了一種帶優先順序的層次關係。例如 java.lang.Object 類,無論哪個類載入器去載入該類,最終都由啟動類載入器載入,因此object 類在程式的各種類載入器環境中都是同一個類。否則的話使用者自定義一個java.lang.Object 類且放在classpath 中,那麼系統中會出現多個Object類,導致程式混亂。
看到ClassLoader原始碼:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先檢查該類是否已經被載入
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 如果沒被當前裝載,則遞迴的到父中裝載
c = parent.loadClass(name, false);
} else {
//裝載器到頂部還沒找到。則在Bootstrap中找
c = findBootstrapClassOrNull(name);
}
} 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;
}
}
loadClass 與 findClass
重寫 loadClass 會覆蓋掉雙親委派機制,這種情況下就有可能會有大量的相同的類被不同的類載入器載入到記憶體中。
重寫 findClass 依然遵循雙親委派模型
Class.forName
Class.forName是一個靜態方法,同樣可以用來載入類。該方法有兩種形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。第一種形式的引數 name表示的是類的全名;initialize表示是否初始化類;loader表示載入時使用的類載入器。
第二種形式則相當於設定了引數 initialize的值為 true,loader的值為當前類的類載入器。
Class.forName的一個很常見的用法是在載入資料庫驅動的時候。如 Class.forName(“org.apache.derby.jdbc.EmbeddedDriver”).newInstance()用來載入 Apache Derby 資料庫的驅動。