
類加載器算是 JVM 的核心技術(shù),也是比較難懂的知識(shí),因此當(dāng)然也就是成為了 java 面試中不可少內(nèi)容,或多或少都會(huì)問(wèn)到一個(gè)兩個(gè)有關(guān) classLoader 的問(wèn)題。所以作為 java developer 我們應(yīng)該吃透這部分內(nèi)容,才能在編程之路上有所突破。
類加載器,顧名思義是加載類,也就是將類放到一個(gè)位置供 JVM 使用,進(jìn)一步具體點(diǎn)就是將讀取 .class 后綴文件中內(nèi)容(內(nèi)容就是字節(jié)碼形式存儲(chǔ)的,字節(jié)碼的本質(zhì)就是一個(gè)字節(jié)數(shù)組 []byte,有特定的復(fù)雜的內(nèi)部格式。)讀取出來(lái)然后以 class 形式保存在內(nèi)存中共 JVM 使用。
懶加載
java 的 classLoader 也是按需加載,而不是一下子將所有的類都加載到 JVM。明顯的實(shí)例就是在調(diào)用一個(gè)類的靜態(tài)方法時(shí)候,只會(huì)加載這個(gè)類靜態(tài)資源而不會(huì)加載這個(gè)類實(shí)例的資源。
雙親委派
我們不用關(guān)心字面意思,首先知道他是一個(gè)概念就行,就是面試提問(wèn)字面量,了解其背后含義才能讓我們更好了解 JVM 加載類機(jī)制。在 Java 中有 3 種類加載器,分別是引導(dǎo)類加載器,擴(kuò)展類加載器和系統(tǒng)類加載器。三者是的關(guān)系是:引導(dǎo)類加載器是擴(kuò)展類加載器的父類,擴(kuò)展類加載器是系統(tǒng)類加載器的父類。雙親(父類)委派模型: 某加載器 每次準(zhǔn)備加載類的時(shí)候,都會(huì)先嘗試委托(其父類)加載器進(jìn)行加載該類。
BootstrapClassLoader 負(fù)責(zé)加載 JVM 運(yùn)行時(shí)核心類,這些類位于 JAVA_HOME/lib/rt.jar 文件中,我們常用內(nèi)置庫(kù) java.xxx.* 都在里面,比如 java.util.、java.io.、java.nio.、java.lang. 等等。這個(gè) ClassLoader 比較特殊,是由 C 代碼實(shí)現(xiàn)的,將其稱之為根加載器。
public class ZClientC {
public static void main(String[] args) {
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
URL url = urls[i];
System.out.println(url.toExternalForm());
}
}
}
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_171/jre/classes
ExtensionClassLoader 負(fù)責(zé)加載 JVM 擴(kuò)展類,比如 swing 系列、內(nèi)置的 js 引擎、xml 解析器 等等,這些庫(kù)名通常以 javax 開頭,它們的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。
System.out.println(System.getProperty("java.ext.dirs"));
ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());
AppClassLoader 才是直接面向用戶的加載器,會(huì)加載 Classpath 環(huán)境變量里定義的路徑中的 jar 包和目錄。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由AppClassLoader 來(lái)加載的。

當(dāng)然我們可以自己寫一個(gè) classloader 在加載類,通過(guò) classLoader 過(guò)程對(duì)上面的內(nèi)容結(jié)合實(shí)際理解一下。這樣就不會(huì)顯得那么枯燥和抽象了。
class MClassLoader extends ClassLoader{
private String className = "";
}
public class ZClientE {
public static void main(String[] args) {
}
}
首先需要復(fù)寫一下 findClass 這個(gè)方法,這個(gè)方法接收 class 名稱作為參數(shù),返回一個(gè)類。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
感興趣可以查看 findClass 的源碼,這個(gè)方法就是拋出了一個(gè)異常。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
private byte[] loadClassData(String className){
InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".","http://") + classExtensionName);
ByteArrayOutputStream byteStr = new ByteArrayOutputStream();
int len = 0;
try {
while((-1 != (len = is.read()))){
byteStr.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
return byteStr.toByteArray();
}
這個(gè)方法負(fù)責(zé)通過(guò)讀取 class 文件來(lái)返回字節(jié)碼數(shù)組同 findClass 方法使用。每一個(gè)類都有一個(gè)類加載器。在程序運(yùn)行時(shí)當(dāng)遇到一個(gè)未知的類,JVM 是如何加載這個(gè)類的呢?使用調(diào)用者(調(diào)用這個(gè) Class)的對(duì)象的 Class 。getClass().getClassLoader() 獲取類的類加載器讀取文件的字節(jié)碼數(shù)組。然后將其字節(jié)碼數(shù)組作為返回值供 findClass 使用
定義一個(gè)類作為加載目標(biāo)類 Greet ,簡(jiǎn)單的 show 方法來(lái)打印一條信息。
package com.zidea.test;
public class Greet {
public void show(){
System.out.println("hello classloader...");
}
}
在 main 方法調(diào)用我們自己類加載器來(lái)加載類
public static void main(String[] args) {
MClassLoader loader = new MClassLoader();
try {
Class<?> greet = loader.findClass("com.zidea.test.Greet");
Object ob = greet.newInstance();
Method md = greet.getMethod("show");
md.invoke(ob);
} catch (Exception var5) {
var5.printStackTrace();
}
}
這是自定義 ClassLoader 完整代碼,
class MClassLoader extends ClassLoader{
private String className = "";
private final String classExtensionName = ".class";
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassData(name);
return defineClass(name,bytes,0,bytes.length);
}
private byte[] loadClassData(String className){
InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".","http://") + classExtensionName);
ByteArrayOutputStream byteStr = new ByteArrayOutputStream();
int len = 0;
try {
while((-1 != (len = is.read()))){
byteStr.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
return byteStr.toByteArray();
}
}
