類加載器負責運行時(Runtime)把class文件動態的加載到虛擬機(JVM)中。同時,類加載器也是JRE的一部分,所以JRE可以不關心底層文件系統,就可以運行Java程序。此外,這些 Java 類不是一次性加載到內存中的,而是在應用程序需要時加載到內存中。
類加載器簡介
首先我們通過一段代碼來看一下Java中的類加載器:
public class ClassLoaderTest {
@Test
public void testClassLoaders() {
System.out.println("Classloader of this class:"
+ ClassLoaderTest.class.getClassLoader());
System.out.println("Classloader of Logging:"
+ Logging.class.getClassLoader());
System.out.println("Classloader of ArrayList:"
+ ArrayList.class.getClassLoader());
// 打印結果
// Classloader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
// Classloader of Logging:sun.misc.Launcher$ExtClassLoader@6325a3ee
// Classloader of ArrayList:null
}
}
從打印結果來看,包含3種類加載器:應用類加載器(Application ClassLoader), 擴展類加載器(Extension ClassLoader)和 一個啟動類加載器(展示位null)。
- 應用類加載器 在類路徑中加載我們自己的文件,它是擴展類加載器的子類。
- 擴展類加載器加載的類是核心 Java 類的擴展,默認放在/lib/ext目錄中,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫。它是啟動類加載器的子類。
- 啟動類加載器是所有其他類的父類,默認放在/lib目錄中,或者-Xbootclasspath參數所指定的路徑中。是虛擬機識別的類庫,用戶無法直接使用。對於ArrayList為什麼打印出來的是null,那是因為啟動類加載器都是用本地方法(native)寫的,而不是Java,所以不會顯示類。
類加載器是如何工作的
上面我們已經知道,類加載器是JRE的一部分。當 JVM 嘗試加載一個類時,類加載器嘗試定位該類並使用完全限定的類名將類信息加載進入runtime。
java.lang.ClassLoader.loadClass()負責將類加入runtime,如果runtime中沒有該類,會首先從父類加載,如果父類沒有加載,然後嘗試從子類加載,這就是雙親委派模型 。如果最後都沒有加載到該類,則會拋出ClassNotFoundException異常。
雙親委派模型: 當一個類加載器收到類加載請求時,它首先不會自己去加載這個類的信息,而是把該請求轉發給父類加載器,依次向上。所以所有的類加載請求都會被傳遞到父類加載器中,只有當父類加載器中無法加載到所需的類,子類加載器才會自己嘗試去加載該類。
在大部分情況下,內置的幾種類加載器已經夠用了,但是在需要從本地硬盤或網絡加載類的場景中,我們可能需要使用自定義類加載器。
public class CustomClassLoader extends ClassLoader {
@Override
public Class findClass(String name) {
byte[] b = loadClassFromFile(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassFromFile(String fileName) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
fileName.replace('.', File.separatorChar) + ".class");
byte[] buffer;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = 0;
try {
while ( (nextValue = inputStream.read()) != -1 ) {
byteStream.write(nextValue);
}
} catch (IOException e) {
e.printStackTrace();
}
buffer = byteStream.toByteArray();
return buffer;
}
}
loadClass方法
public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {}
這裡需要注意的是loadClass的第二個參數,當需要解析類的時候需要將該值設置為true,然而,我們並不總是需要解析一個類。 如果我們只需要確定類是否存在,那麼 resolve 參數設置為 false。
如class.forName加載類時resolve就為true;而classLoader.loadClass時默認resolve為false。
關鍵字: getClassLoader public 子類