【37期】請你詳細說說類加載流程,類加載機制及自定義類加載器
閱讀本文大概需要 12?分鐘。
來自:juejin.im/post/5cffa528e51d4556da53d091
一、引言
二、類的加載、鏈接、初始化
1、加載
1.1、加載的class來源
從本地文件系統(tǒng)內(nèi)加載class文件
從JAR包加載class文件
通過網(wǎng)絡(luò)加載class文件
把一個java源文件動態(tài)編譯,并執(zhí)行加載。
2、類的鏈接
2.1、驗證
2.2、準備
2.3、解析
3、類的初始化
在定義時初始化
在靜態(tài)初始化塊內(nèi)初始化
3.1、< clinit>方法相關(guān)
public?class?Test?{
????static?int?A?=?10;
????static?{
????????A?=?20;
????}
}
class?Test1?extends?Test?{
????private?static?int?B?=?A;
????public?static?void?main(String[]?args)?{
????????System.out.println(Test1.B);
????}
}
//輸出結(jié)果
//20
public?interface?InterfaceInitTest?{
????long?A?=?CurrentTime.getTime();
}
interface?InterfaceInitTest1?extends?InterfaceInitTest?{
????int?B?=?100;
}
class?InterfaceInitTestImpl?implements?InterfaceInitTest1?{
????public?static?void?main(String[]?args)?{
????????System.out.println(InterfaceInitTestImpl.B);
????????System.out.println("---------------------------");
????????System.out.println("當前時間:"+InterfaceInitTestImpl.A);
????}
}
class?CurrentTime?{
????static?long?getTime()?{
????????System.out.println("加載了InterfaceInitTest接口");
????????return?System.currentTimeMillis();
????}
}
//輸出結(jié)果
//100
//---------------------------
//加載了InterfaceInitTest接口
//當前時間:1560158880660
public?class?MultiThreadInitTest?{
????static?int?A?=?10;
????static?{
???????????System.out.println(Thread.currentThread()+"init?MultiThreadInitTest");
????????try?{
????????????TimeUnit.SECONDS.sleep(10);
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????}
????public?static?void?main(String[]?args)?{
????????Runnable?runnable?=?()?->?{
????????????System.out.println(Thread.currentThread()?+?"start");
????????????System.out.println(MultiThreadInitTest.A);
????????????System.out.println(Thread.currentThread()?+?"run?over");
????????};
????????Thread?thread1?=?new?Thread(runnable);
????????Thread?thread2?=?new?Thread(runnable);
????????thread1.start();
????????thread2.start();
????}
}
//輸出結(jié)果
//Thread[main,5,main]init?MultiThreadInitTest
//Thread[Thread-0,5,main]start
//10
//Thread[Thread-0,5,main]run?over
//Thread[Thread-1,5,main]start
//10
//Thread[Thread-1,5,main]run?over
3.2、類初始化時機
當虛擬機啟動時,初始化用戶指定的主類;
當遇到用以新建目標類實例的new指令時,初始化new指令的目標類;
當遇到調(diào)用靜態(tài)方法或者使用靜態(tài)變量,初始化靜態(tài)變量或方法所在的類;
子類初始化過程會觸發(fā)父類初始化;
如果一個接口定義了default方法,那么直接實現(xiàn)或者間接實現(xiàn)該接口的類的初始化,會觸發(fā)該接口初始化;
使用反射API對某個類進行反射調(diào)用時,初始化這個類;
Class.forName()會觸發(fā)類的初始化
3.3、final定義的初始化
public?class?StaticInnerSingleton?{
????/**
?????*?使用靜態(tài)內(nèi)部類實現(xiàn)單例:
?????* 1:線程安全
?????* 2:懶加載
?????* 3:非反序列化安全,即反序列化得到的對象與序列化時的單例對象不是同一個,違反單例原則
?????*/
????private?static?class?LazyHolder?{
????????private?static?final?StaticInnerSingleton?INNER_SINGLETON?=?new?StaticInnerSingleton();
????}
????private?StaticInnerSingleton()?{
????}
????public?static?StaticInnerSingleton?getInstance()?{
????????return?LazyHolder.INNER_SINGLETON;
????}
}
3.4、ClassLoader只會對類進行加載,不會進行初始化
public?class?Tester?{
????static?{
????????System.out.println("Tester類的靜態(tài)初始化塊");
????}
}
class?ClassLoaderTest?{
????public?static?void?main(String[]?args)?throws?ClassNotFoundException?{
????????ClassLoader?classLoader?=?ClassLoader.getSystemClassLoader();
????????//下面語句僅僅是加載Tester類
????????classLoader.loadClass("loader.Tester");
????????System.out.println("系統(tǒng)加載Tester類");
????????//下面語句才會初始化Tester類
????????Class.forName("loader.Tester");
????}
}
//輸出結(jié)果
//系統(tǒng)加載Tester類
//Tester類的靜態(tài)初始化塊
三、類加載器
1、JVM類加載器分類
1.1、Bootstrap ClassLoader
public?class?BootstrapTest?{
????public?static?void?main(String[]?args)?{
????????//獲取根類加載器所加載的全部URL數(shù)組
????????URL[]?urLs?=?Launcher.getBootstrapClassPath().getURLs();
????????Arrays.stream(urLs).forEach(System.out::println);
????}
}
//輸出結(jié)果
//file:/C:/SorftwareInstall/java/jdk/jre/lib/resources.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/rt.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/sunrsasign.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jsse.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jce.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/charsets.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jfr.jar
//file:/C:/SorftwareInstall/java/jdk/jre/classes

1.2 、Extension ClassLoader
1.3、 System ClassLoader
四、類加載機制
1.1、JVM主要的類加載機制。
全盤負責:當一個類加載器負責加載某個Class時,該Class所依賴和引用的其他Class也由該類加載器負責載入,除非顯示使用另一個類加載器來載入。
父類委托(雙親委派):先讓父加載器試圖加載該Class,只有在父加載器無法加載時該類加載器才會嘗試從自己的類路徑中加載該類。
緩存機制:緩存機制會將已經(jīng)加載的class緩存起來,當程序中需要使用某個Class時,類加載器先從緩存區(qū)中搜尋該Class,只有當緩存中不存在該Class時,系統(tǒng)才會讀取該類的二進制數(shù)據(jù),并將其轉(zhuǎn)換為Class對象,存入緩存中。這就是為什么更改了class后,需要重啟JVM才生效的原因。

public?class?ClassloaderPropTest?{
????public?static?void?main(String[]?args)?throws?IOException?{
????????//獲取系統(tǒng)類加載器
????????ClassLoader?systemClassLoader?=?ClassLoader.getSystemClassLoader();
????????System.out.println("系統(tǒng)類加載器:"?+?systemClassLoader);
????????/*
????????獲取系統(tǒng)類加載器的加載路徑——通常由CLASSPATH環(huán)境變量指定,如果操作系統(tǒng)沒有指定
????????CLASSPATH環(huán)境變量,則默認以當前路徑作為系統(tǒng)類加載器的加載路徑
?????????*/
????????Enumeration?eml?=?systemClassLoader.getResources("");
????????while?(eml.hasMoreElements())?{
????????????System.out.println(eml.nextElement());
????????}
????????//獲取系統(tǒng)類加載器的父類加載器,得到擴展類加載器
????????ClassLoader?extensionLoader?=?systemClassLoader.getParent();
????????System.out.println("系統(tǒng)類的父加載器是擴展類加載器:"?+?extensionLoader);
????????System.out.println("擴展類加載器的加載路徑:"?+?System.getProperty("java.ext.dirs"));
????????System.out.println("擴展類加載器的parant:"?+?extensionLoader.getParent());
????}
}
//輸出結(jié)果
//系統(tǒng)類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
//file:/C:/ProjectTest/FengKuang/out/production/FengKuang/
//系統(tǒng)類的父加載器是擴展類加載器:sun.misc.Launcher$ExtClassLoader@1540e19d
//擴展類加載器的加載路徑:C:\SorftwareInstall\java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
//擴展類加載器的parant:null
1.2、類加載流程圖

五、創(chuàng)建并使用自定義類加載器
1、自定義類加載分析
protected Class?loadClass(String name, boolean resolve):name為類名,resove如果為true,在加載時解析該類。
protected Class?findClass(String name)?:根據(jù)指定類名來查找類。
protected?Class>?loadClass(String?name,?boolean?resolve)
????????throws?ClassNotFoundException
????{
????????synchronized?(getClassLoadingLock(name))?{
????????????//第一步,先從緩存里查看是否已經(jīng)加載
????????????Class>?c?=?findLoadedClass(name);
????????????if?(c?==?null)?{
????????????????long?t0?=?System.nanoTime();
????????????????try?{
????????????????//第二步,判斷父加載器是否為null
????????????????????if?(parent?!=?null)?{
????????????????????????c?=?parent.loadClass(name,?false);
????????????????????}?else?{
????????????????????????c?=?findBootstrapClassOrNull(name);
????????????????????}
????????????????}?catch?(ClassNotFoundException?e)?{
????????????????????//?ClassNotFoundException?thrown?if?class?not?found
????????????????????//?from?the?non-null?parent?class?loader
????????????????}
????????????????if?(c?==?null)?{
???????????????????//第三步,如果前面都沒有找到,就會調(diào)用findClass方法
????????????????????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;
????????}
????}
判斷此類是否已經(jīng)加載;
如果父加載器不為null,則使用父加載器進行加載;反之,使用根加載器進行加載;
如果前面都沒加載成功,則使用findClass方法進行加載。
2、實現(xiàn)自定義類加載器
public?class?Hello?{
???public?void?test(String?str){
???????System.out.println(str);
???}
}
public?class?MyClassloader?extends?ClassLoader?{
????/**
?????*?讀取文件內(nèi)容
?????*
?????*?@param?fileName?文件名
?????*?@return
?????*/
????private?byte[]?getBytes(String?fileName)?throws?IOException?{
????????File?file?=?new?File(fileName);
????????long?len?=?file.length();
????????byte[]?raw?=?new?byte[(int)?len];
????????try?(FileInputStream?fin?=?new?FileInputStream(file))?{
????????????//一次性讀取Class文件的全部二進制數(shù)據(jù)
????????????int?read?=?fin.read(raw);
????????????if?(read?!=?len)?{
????????????????throw?new?IOException("無法讀取全部文件");
????????????}
????????????return?raw;
????????}
????}
????@Override
????protected?Class>?findClass(String?name)?throws?ClassNotFoundException?{
????????Class?clazz?=?null;
????????//將包路徑的(.)替換為斜線(/)
????????String?fileStub?=?name.replace(".",?"/");
????????String?classFileName?=?fileStub?+?".class";
????????File?classFile?=?new?File(classFileName);
????????//如果Class文件存在,系統(tǒng)負責將該文件轉(zhuǎn)換為Class對象
????????if?(classFile.exists())?{
????????????try?{
????????????????//將Class文件的二進制數(shù)據(jù)讀入數(shù)組
????????????????byte[]?raw?=?getBytes(classFileName);
????????????????//調(diào)用ClassLoader的defineClass方法將二進制數(shù)據(jù)轉(zhuǎn)換為Class對象
????????????????clazz?=?defineClass(name,?raw,?0,?raw.length);
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????????//如果clazz為null,表明加載失敗,拋出異常
????????if?(null?==?clazz)?{
????????????throw?new?ClassNotFoundException(name);
????????}
????????return?clazz;
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????String?classPath?=?"loader.Hello";
????????MyClassloader?myClassloader?=?new?MyClassloader();
????????Class>?aClass?=?myClassloader.loadClass(classPath);
????????Method?main?=?aClass.getMethod("test",?String.class);
????????System.out.println(main);
????????main.invoke(aClass.newInstance(),?"Hello?World");
????}
}
//輸出結(jié)果
//Hello?World
六、總結(jié)
《瘋狂java講義(第3版)》
《深入理解java虛擬機++JVM高級特性與最佳實踐》
推薦閱讀:
【34期】談?wù)劄槭裁匆鸱謹?shù)據(jù)庫?有哪些方法?
微信掃描二維碼,關(guān)注我的公眾號
朕已閱?

