淺談Java類加載器
參考內容:
- 深入理解Java虛擬機(JVM高級特性與最佳實踐) ——周志明老師
- 尚硅谷深入理解JVM教學視頻——宋紅康老師
我們都知道Java的類加載器結構為下圖所示(JDK8及之前,JDK9進行了模塊化):
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Yf5OjvzY-1607086431594)(imgs/image-20200705105151258-1601293177133.png)]](https://img.uj5u.com/2020/12/05/201315051321031.png)
關于三層類加載器、雙親委派機制,本文不再板書,讀者可自行百度,
那么在JDK的原始碼中,三層結構的具體實作是怎么樣的呢?
Bootstrap ClassLoader(引導類加載器)
引導類加載器是由C++實作的,并非Java代碼實作,所以在Java代碼中是無法獲取到該類加載器的,
一般大家都稱類加載器分為四種(引導類、擴展類、系統類以及用戶自定義的類加載器),但其實在JVM虛擬機規范中的支持兩種型別的類加載器,分別為引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader),所以擴展類和系統類也可以統稱為自定義類加載器,

Extension ClassLoader(擴展類加載器)和Appclass Loader(系統類加載器)
擴展類加載器和系統類加載器都是由Java語言撰寫,具體實作為sum.misc.Launcher中的兩個內部類ExtClassLoader和AppClassLoader實作,我們進入到LaunchLacher這個類中看看(這個類在oracle jdk是沒有公開原始碼的,需要看具體原始碼的讀者可以下載open jdk中查看具體原始碼,筆者這里就只是使用IDEA反編譯后生成的代碼進行決議):
首先是Laucncher的構造方法:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// 獲取擴展類加載器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 獲取系統類加載器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 此處是將系統類加載器設定為當前執行緒的背景關系加載器
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
可以看到在Launcher的構造方法中定義了一個Launcher.ExtClassLoader型別的區域變數var1(這里是反編譯后的變數名),并呼叫Launcher.ExtClassLoader.getExtClassLoader()方法給該區域變數賦值,以及呼叫Launcher.AppClassLoader.getAppClassLoader(var1);給實體變數(型別為Launcher.AppClassLoader)賦值,需要注意的是,在給系統類加載器賦值時,將擴展類加載器作為引數傳入到了方法中,
同時,在構造方法中,將系統類加載器設定為了當前執行緒的背景關系類加載器,關于背景關系類加載器,主要用于基礎型別呼叫回用戶代碼時方法父類加載器區請求子類加載器完成類加載的行為,主要用于JDBC、JNDI等SPI服務提供者介面,這里不詳細展開,
上述原始碼中的**getExtClassLoader()與getAppClassLoader()**方法原始碼如下:
getExtClassLoader()是Launcher中的內部類ExtClassLoader(擴展類加載器)的一個靜態方法:
// 這是ExtClassLoader類內部的定義
private static volatile Launcher.ExtClassLoader instance;// 單例模式實體物件
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
// 從這里可以看出,ExtClassLoader是一個由double-checking形成的懶漢式單例物件
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader(); // 創建ExtClassLoader
}
}
}
return instance;
}
// createExtClassLoader()方法
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
return new Launcher.ExtClassLoader(var1); // 呼叫構造方法
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
// ExtClassLoader的構造方法
public ExtClassLoader(File[] var1) throws IOException {
// 此處第二個引數需要格外注意!!,我們進入父類的構造方法查看該引數是什么
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
// 父類URLClassLoader的構造方法
// 此處的第二個引數是父類構造器的參考,也就解釋了為什么在呼叫獲得ExtClassLoader的
public URLClassLoader(URL[] urls, ClassLoader parent, getParent()方法獲取父類構造器為null
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
acc = AccessController.getContext();
ucp = new URLClassPath(urls, factory, acc);
}
getAppClassLoader()是Launcher中的內部類AppClassLoader(系統類加載器)的一個靜態方法:
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0); // 與擴展類加載器不同的是,系統類加載器并不是單例模式的
}
});
}
// AppClassLoader的構造方法
AppClassLoader(URL[] var1, ClassLoader var2) {
// 這里的var2 對應上述getAppClassLoader()方法中的var0,而var0對應的就是Launcher的構造方法中獲取到的ExtClassLoader
// 在ExtClassLoader原始碼的分析中,我們知道這個var2代表的就是父類構造器,所以此處就是將AppClassLoader的父類設定為ExtClassLoader
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
通過上述兩個方法,就可以解釋為什么在獲取擴展類加載器的父類時為null(即引導加載器),以及不同類加載器看似是繼承(Inheritance)關系,實際上是包含關系,在下層加載器中,包含著上層加載器的參考,
ClassLoader抽象類
上述的ExtClassLoader和AppClassLoader均繼承于ClassLoader類,ClassLoader抽象類也是類加載機制的基石,接下來我們就進入到該類中,看看它的一些主要方法,
-
public final classLoader getParent()
- 回傳該類加載器的超類加載器
-
public Class<?>loadclass(String name) throws ClassNotFoundException
- 加載名稱為name的類,回傳結果為java.lang.Class類的實體,如果找不到類,則返ClassNotFoundException例外,該方法中的邏輯就是雙親委派模式的實作,
-
protected class<?> findClass(string name)throws ClassNotFoundException
- 查找二進制名稱為name的類,回傳結果為java.lang.Class類的實體,這是一個受保護的方法,JVM鼓勵我們重寫此方法,需要自定義加載器遵循雙親委托機制,該方法會在檢查完父類加載器之后被loadClass()方法呼叫,
- 在JDK1.2之前,在自定義類加載時,總會去繼承ClassLoader類并重寫loadClass方法,從而實作自定義的類加載類,但是在JDK1.2之后已不再建議用戶去覆寫loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中,從前面的分析可知, findClass()方法是在loadClass()方法中被呼叫的,當loadclass()方法中父加載器加載失敗后,則會呼叫自己的findClass()方法來完成類加載,這樣就可以保證自定義的類加載器也符合雙親委托模式,
- 需要注意的是ClassLoader類中并沒有實作findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException例外,同時應該知道的是findClass方法通常是和defineClass方法一起使用的,一般情況下,在自定義類加載器時,會直接覆寫ClassLoader的findClass()方法并撰寫加載規則,取得要加載類的位元組碼后轉換成流,然后呼叫defineClass()方法生成類的Class物件,
-
protected final Class<?> defineClass(String name, byte[] b, int off,int len)
- 根據給定的位元組陣列b轉換為Class的實體,off和len引數表示實際Class資訊在byte陣列中的位置和長度,其中byte陣列b是ClassLoader從外部獲取的,這是受保護的方法,只有在自定義ClassLoader子類中可以使用,
- defineClass()方法是用來將byte位元組流決議郕VM能夠識別的cClass物件(ClassLoader中已實作該方法邏輯),通過這個方法不僅能夠通過class檔案實體化class物件,也可以通過其他方式實體化class物件,如通過網路接收一個類的位元組碼,然后轉換為byte位元組流創建對應的Class物件,
- defineClass()方法通常與findClass()方法一起使用,一般情況下,在自定義類加載器時,會直接覆寫ClassLoader的findClass()方法并撰寫加載規則,取得要加載炎的位元組碼后轉換成流,然后呼叫defineClass()方法生成類的Class物件,
-
protected final void resoiveClass(class<?> c)
- 鏈接指定的一個Java類,使用該方法可以使用類的Class物件創建完成的同時也被決議,前面我們說鏈接階段主要是對位元組碼進行驗證,為類變數分配記憶體并設定初始值同時將位元組碼檔案中的符號參考轉換為直接參考,
-
protected final Class<?> findLoadedClass(String name)
- 查找名稱為name的已經被加載過的類,回傳結果為java.lang.Class類的實體,這個方法是final方法,無法被修改,
-
private final ClassLoader parent;
- 它也是一個ClassLoader的實體,這個欄位所表示的ClassLoader也稱為這個ClassLoader的雙親,在類加載的程序中,classLoader可能會將某些請求交予自己的雙親處理,
關于這些方法,不一一展開,主要看一下loadClass()和findClass(),
loadClass()
public Class<?> loadClass(String name) throws ClassNotFoundException {
// loadClass呼叫多載含有兩個引數的loadClass,其中第二個引數表示在加載時是否決議,默認為false
return loadClass(name, false);
}
// 含有兩個引數的多載loadClass方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{// resolve:true->加載class的同時進行決議操作
synchronized (getClassLoadingLock(name)) {// 同步操作,保證只能加載一次
// 首先在快取中判斷是否已經加載同名的類
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
// 此處就是雙親委派機制的具體實作,其實就是讓父類加載器先去加載,
try {
// 獲取當前類加載器的父類加載器
if (parent != null) {
// 如果存在父類加載器,則呼叫父類加載器的loadClass進行加載(雙親委派)
c = parent.loadClass(name, false);
} else {
// parent == null:父類加載器是引導類加載器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 當前類加載器的父類加載器未加載此類 or 當前類加載器未加載此類
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 呼叫當前類加載器的findClass()
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;
}
}
findClass()
//在ClassLoader中的findClass()方法
rotected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可以看到,在ClassLoader中的findCLass()方法直接拋出例外,所以具體的實作是由子類進行重寫實作了;在ClassLoader的子類SecureClassLoader的子類URLClassLoader中對該方法進行了重寫,
URLClassLoader中的findCLass()方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");// 類名路徑字串格式替換
Resource res = ucp.getResource(path, false);// 獲得class源檔案
if (res != null) {
try {
// 呼叫defineClass()方法獲得要加載的類對應的Class物件,
// defineClass()的作用就是根據給定的class源檔案回傳一個對應的Class物件
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
最后補充一點關于陣列類加載的細節
陣列類的Class物件,不是由類加載器去創建的,而是在Java運行期JVM根據需要自動創建的,對于陣列類的類加載器來說,是通過Class.getClassLoader()回傳的,與陣列當中元素型別的類加載器是一樣的,如果陣列當中的元素型別是基本資料型別,陣列類是沒有類加載器的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/230291.html
標籤:java
