通讀這篇文章你會(huì)知道如何回答以下問(wèn)題:
- Java自帶的三大加載器加載的jar位置都是在哪里?
- 三大加載器之間的關(guān)系是怎么樣的? 在代碼中是如何體現(xiàn)的?
- 雙親委派模型是什? 代碼中如何體現(xiàn)這種模式的應(yīng)用? 這種模式的不足是什么?
- 上下文加載器存在的作用是什么?應(yīng)用的場(chǎng)景有哪些?
java三大加載器加載的jar位置
知道每個(gè)加載器加載什么位置的jar,這對(duì)后面分析委托機(jī)制會(huì)起到作用。
Java語(yǔ)言自帶的有三個(gè)類加載器
Bootstrap CLassloder
Extention ClassLoader
-
AppClassLoader
以下是輸出各個(gè)加載器的加載jar的位置,這些路徑可以通過(guò)虛擬機(jī)參數(shù)進(jìn)行修改。public static void testClassLoader() { System.out.println("BootstrapClassLoader:"); String property = System.getProperty("sun.boot.class.path"); Arrays.stream(property.split(";")).forEach(System.out::println); System.out.println("ExtClassLoader :"); property = System.getProperty("java.ext.dirs"); Arrays.stream(property.split(";")).forEach(System.out::println); System.out.println("AppClassLoader :"); property = System.getProperty("java.class.path"); Arrays.stream(property.split(";")).forEach(System.out::println); }
- Bootstrap CLassloder 加載位置如下:
JDK\jdk1.8\jre\lib\resources.jar
JDK\jdk1.8\jre\lib\rt.jar
JDK\jdk1.8\jre\lib\sunrsasign.jar
JDK\jdk1.8\jre\lib\jsse.jar
JDK\jdk1.8\jre\lib\jce.jar
JDK\jdk1.8\jre\lib\charsets.jar
JDK\jdk1.8\jre\lib\jfr.jar
JDK\jdk1.8\jre\classes - Extention ClassLoader加載位置如下:
C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext; - AppClassLoader 加載位置如下:
就是你項(xiàng)目配置的jar路徑以及工程生成的classes的位置,如maven會(huì)是 target\classes,或者bin目錄; - 這些路徑的可以從sun.misc.Launcher類得知;
加載器之間的關(guān)系
-
如果類是在boostrapClassLoder下加載,無(wú)法獲取其加載器
從報(bào)錯(cuò)的情況,因?yàn)閎ootstrapclassloader有加載過(guò)rt.jar,這個(gè)從前面可以看出來(lái),Integer類是rt.jar里面的類,所以Integer是由bootstrapclassloader進(jìn)行加載的沒(méi)錯(cuò),那為啥是空指針呢?
1-1
來(lái)一張extclassloader的繼承圖,AppClassLoader也一樣的繼承關(guān)系
圖1
這個(gè)空指針我是這么理解的:凡是有boostraploader進(jìn)行加載的類,都是獲取不到此類的加載器,因?yàn)锽ootstrap ClassLoader是由C/C++編寫(xiě)的,它本身是虛擬機(jī)的一部分,所以它并不是一個(gè)JAVA類,也就是無(wú)法在java代碼中獲取它的引用; -
ExtClassLoadder的父加載器是null
接下來(lái)從源碼角度來(lái)看下,圖2輸出的原因(即extClassLoader的父記載器為啥null)
圖2
關(guān)鍵從parent方法入手,可以發(fā)現(xiàn)在圖一的繼承關(guān)系中找到parent方法是在ClassLoader中如圖3,從idea點(diǎn)擊過(guò)去也可以找到,從方法來(lái)看parent是final成員變量,所以找到構(gòu)造方法,就知道如何它是如何初始化了
圖3
從下面的代碼可以看出parent初始化有兩種方式:
一種是指定ClassLoader;
第二種如果沒(méi)有指定則通過(guò)initSystemClassLoader方法進(jìn)行初始化,這個(gè)方法獲取classLoader方式是從Launcher類的getClassLoader方法獲取的;protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops != null) { if (oops instanceof Error) { throw (Error) oops; } else { // wrap the exception throw new Error(oops); } } } sclSet = true; } }
Launcher類的classLoader方法初始化如下:
從代碼可知Launcher.getClassLoader就是獲取appclassLoader,意味著一個(gè)類的父加載器如果沒(méi)有指定,則是默認(rèn)就是AppClassLoader。
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);
}
}
public ClassLoader getClassLoader() {
return this.loader;
}
但是這個(gè)還是無(wú)法解釋extClassLoader的父加載器是null,請(qǐng)看Laucher類中ExtClassLoader類的構(gòu)造法方方法,指定的父加載器就是null,所以從parent變量的獲取追蹤到extClassloader是由于指定null父加載器,所以導(dǎo)致extClassLoader獲取父加載器是null,
而AppClassLoader獲取父加載器是extClassLoader,是因?yàn)橹付薳xtClassLoader為自己的父加載器;


-
從源碼角度理解雙親委托機(jī)制以及boostrapClassLoader為啥可以作為extClassLoader的父加載器
雙親委托是什么,看過(guò)博主的文章就知道了,一句話概括
委托是從下向上,然后具體查找過(guò)程卻是自上至下
從ClassLoader的loadClass可以明白雙親委托機(jī)制過(guò)程,同時(shí)知道如果自定義的ClassLoader是覆蓋findClass,而不是loadClass,采用這種方式進(jìn)行加載可以避免java核心api中定義的類型被自定義的加載器加載,從而出現(xiàn)多個(gè)

-
自定義加載器,自己也寫(xiě)個(gè),跟博主一樣,然后調(diào)試了一遍,對(duì)加載過(guò)程再熟悉一遍
public class MyClassLoader extends ClassLoader{ private String filePath; MyClassLoader(String filePath) { this.filePath = filePath; } @Override public Class<?> findClass(String name) throws ClassNotFoundException { int i = name.lastIndexOf(".") +1; String s = name.substring(i) + ".class"; File file = new File(filePath, s); try { FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; try { while ((len = is.read()) != -1) { bos.write(len); } } catch (IOException e) { e.printStackTrace(); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass(name,data,0,data.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.findClass(name); } public static void main(String[] args) throws Exception{ MyClassLoader classLoader = new MyClassLoader("D:\\"); Class<?> aClass = classLoader.loadClass("com.example.Product"); if (aClass != null) { Object o = aClass.newInstance(); Method ok = aClass.getDeclaredMethod("ok"); ok.invoke(o); } } } 打破雙親委派模型------引出上下文類加載器
雙親委派模型的加載方式是從底層加載器到頂層加載器進(jìn)行加載,但是如果頂層加載器要加載來(lái)自底層加載器的類時(shí),此時(shí)就傳統(tǒng)加載模式就無(wú)法完成了。
這種情況會(huì)出現(xiàn)在SPI使用中,引用一段話來(lái)說(shuō)明SPI加載的情況
SPI機(jī)制簡(jiǎn)介
SPI的全名為Service Provider Interface,主要是應(yīng)用于廠商自定義組件或插件中。在java.util.ServiceLoader的文檔里有比較詳細(xì)的介紹。簡(jiǎn)單的總結(jié)下java SPI機(jī)制的思想:我們系統(tǒng)里抽象的各個(gè)模塊,往往有很多不同的實(shí)現(xiàn)方案,比如日志模塊、xml解析模塊、jdbc模塊等方案。面向的對(duì)象的設(shè)計(jì)里,我們一般推薦模塊之間基于接口編程,模塊之間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼。一旦代碼里涉及具體的實(shí)現(xiàn)類,就違反了可拔插的原則,如果需要替換一種實(shí)現(xiàn),就需要修改代碼。為了實(shí)現(xiàn)在模塊裝配的時(shí)候能不在程序里動(dòng)態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制。 Java SPI就是提供這樣的一個(gè)機(jī)制:為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。有點(diǎn)類似IOC的思想,就是將裝配的控制權(quán)移到程序之外,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要。
SPI具體約定
Java SPI的具體約定為:當(dāng)服務(wù)的提供者提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個(gè)以服務(wù)接口命名的文件。該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候,就能通過(guò)該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名,并裝載實(shí)例化,完成模塊的注入?;谶@樣一個(gè)約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類,而不需要再代碼里制定。jdk提供服務(wù)實(shí)現(xiàn)查找的一個(gè)工具類:java.util.ServiceLoader。
以下面這段代碼來(lái)說(shuō)明雙親委派模型的 逆向加載
String url = "jdbc:mysql://localhost:3306/mysql";
//通過(guò)java庫(kù)獲取數(shù)據(jù)庫(kù)連接
Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");
首先調(diào)用靜態(tài)方法必將導(dǎo)致調(diào)用者會(huì)進(jìn)行初始化,所以DriverMananger類將初始化,如果類中有靜態(tài)代碼塊必將執(zhí)行,下面是DriverManager類的代碼:


class.forName()加載用的是調(diào)用者的Classloader,這個(gè)調(diào)用者DriverManager是在rt.jar中的,ClassLoader是啟動(dòng)類加載器,而com.mysql.jdbc.Driver肯定不在<JAVA_HOME>/lib下,所以肯定是無(wú)法加載mysql中的這個(gè)類的。這就是雙親委派模型的局限性了,父級(jí)加載器無(wú)法加載子級(jí)類加載器路徑中的類
此時(shí)如何抉擇呢,按照目前情況來(lái)分析,這個(gè)mysql的drvier只有應(yīng)用類加載器能加載,那么我們只要在啟動(dòng)類加載器中有方法獲取應(yīng)用程序類加載器,然后通過(guò)它去加載就可以了。這就是所謂的線程上下文加載器
這loader如何獲取的,可以從load方法中看出:

所以這邊就可以看出要通過(guò)頂層加載器去加載底層加載器的類時(shí),通過(guò)上下文加載器實(shí)現(xiàn);



