心得體會:學(xué)習(xí)不僅僅只是看教程册赛,最好能夠想出代碼實例去驗證自己對某個方面的理解和判斷,這樣不僅能加深理解,還能夠在未來的應(yīng)用開發(fā)中使用到票髓。
前言
本篇文章針對Java 8 之前的類加載結(jié)構(gòu),Java 9做了不少改變铣耘,有興趣可以查看相關(guān)資料洽沟。
虛擬機(jī)類加載機(jī)制
類從被加載到虛擬機(jī)內(nèi)存開始,到卸載出內(nèi)存為止涡拘,它的整個生命周期包括:
1. 加載
通過一個類的全限定名來獲取定義此類的二進(jìn)制流玲躯。
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為運行時數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種訪問入口鳄乏。
注意跷车,這里第1條中的二進(jìn)制字節(jié)流并不只是單純地從Class文件中獲取,比如它還可以從Jar包中獲取橱野、從網(wǎng)絡(luò)中獲刃嘟伞(最典型的應(yīng)用便是Applet)、由其他文件生成(JSP應(yīng)用)等水援。
2. 鏈接:
驗證:確保被加載類的正確性密强;
準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值蜗元;
解析:把類中的符號引用轉(zhuǎn)換為直接引用或渤;(解析階段則不一定,它在某些情況下可以在初始化階段之后開始)
3. 初始化
jvm有嚴(yán)格的規(guī)定(五種情況):
遇到new奕扣,getstatic薪鹦,putstatic,invokestatic這4條字節(jié)碼指令時,假如類還沒進(jìn)行初始化池磁,
則馬上對其進(jìn)行初始化工作奔害。
其實就是3種情況:用new實例化一個類時、讀取或者設(shè)置類的靜態(tài)字段時(不包括被final修飾的靜態(tài)字段地熄,
因為他們已經(jīng)被塞進(jìn)常量池了)华临、以及執(zhí)行靜態(tài)方法的時候。使用java.lang.reflect.*的方法對類進(jìn)行反射調(diào)用的時候端考,
如果類還沒有進(jìn)行過初始化雅潭,馬上對其進(jìn)行。初始化一個類的時候跛梗,如果他的父親還沒有被初始化寻馏,則先去初始化其父親。
當(dāng)jvm啟動時核偿,用戶需要指定一個要執(zhí)行的主類(包含static void main(String[] args)的那個類)诚欠,
則jvm會先去初始化這個類。用Class.forName(String className);來加載類的時候漾岳,也會執(zhí)行初始化動作轰绵。
注意:ClassLoader的loadClass(String className);方法只會加載并編譯某類,并不會對其執(zhí)行初始化尼荆。
對于這5種會觸發(fā)類初始化的場景左腔,虛擬機(jī)使用了一個很強(qiáng)烈的限定語,“有且只有”捅儒,這5種場景的行為稱為對一個類進(jìn)行主動引用液样。除此之外,所有引用類的方法都不會觸發(fā)初始化巧还。
- 如:通過子類引用父類的靜態(tài)字段鞭莽,不會導(dǎo)致子類初始化;
- 通過數(shù)組定義引用類麸祷,不會觸發(fā)此類的初始化澎怒;
- 常量在編譯階段會存入調(diào)用類的常量池中,本質(zhì)上沒有直接引用到定義常量的類阶牍,因此不會觸發(fā)定義常量的類的初始化喷面。
初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的 走孽。
這里借網(wǎng)絡(luò)上一張圖片總結(jié)一下,有興趣可參考:Java面試相關(guān)(一)-- Java類加載全過程(覺得圖畫的很細(xì)致惧辈,如若侵權(quán),請聯(lián)系作者)
雙親委派模型
看結(jié)構(gòu)圖(組合關(guān)系磕瓷,非繼承關(guān)系)
從圖中我們發(fā)現(xiàn)除啟動類加載器外盒齿,每個加載器都有父的類加載器。
雙親委派機(jī)制:如果一個類加載器在接到加載類的請求時,它首先不會自己嘗試去加載這個類县昂,而是把這個請求任務(wù)委托給父類加載器去完成,依次遞歸陷舅,如果父類加載器可以完成類加載任務(wù)倒彰,就成功返回;只有父類加載器無法完成此加載任務(wù)時莱睁,才自己去加載待讳。
優(yōu)勢:Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系,避免了重復(fù)加載類仰剿,保障了Java類型體系的安全创淡。
當(dāng)然你也可以破壞雙親委派模型,尤其在Android插件化中南吮。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);//判斷類是否已經(jīng)加載過
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);//父類加載器優(yōu)先加載
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);//調(diào)用當(dāng)前類加載器的findClass方法進(jìn)行加載
// this is the defining class loader; record the stats
}
}
return c;
}
源碼分析:
簡單來說琳彩,java的雙親委派機(jī)制分為三個過程,在ClassLoader的loadClass方法中會先判斷該類是否已經(jīng)加載部凑,若加載了直接返回露乏,若沒加載過則先調(diào)用父類加載器的loadClass方法進(jìn)行類加載,若父類加載器沒有找到涂邀,則會調(diào)用當(dāng)前正在查找的類加載器的findClass方法進(jìn)行加載瘟仿。
如果想保證自定義的類加載器符合雙親委派機(jī)制,則覆寫findClass方法比勉;如果想打破雙親委派機(jī)制劳较,則覆寫loadClass方法。
破壞雙親委派機(jī)制:
public class MyClassLoader extends DexClassLoader{
public MyClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if(xxx){//條件判斷是否自己加載
return this.loadClass(name);
}else{//雙親委派機(jī)制加載
return super.loadClass(name, resolve);
}
}
}
父類浩聋,子類加載順序
父類代碼:
public class A{
static{
System.out.println("父類-靜態(tài)代碼塊");
}
{
System.out.println("父類-非靜態(tài)代碼塊");
}
public A(){
System.out.println("父類-構(gòu)造方法");
}
}
子類代碼:
public class B extends A{
static{
System.out.println("子類-靜態(tài)代碼塊");
}
{
System.out.println("子類-非靜態(tài)代碼塊");
}
public B(){
System.out.println("子類-構(gòu)造方法");
}
}
測試一下:
public class Test{
public static void main(String[] args) {
B b = new B();
}
}
看看效果:
父類-靜態(tài)代碼塊
子類-靜態(tài)代碼塊
父類-非靜態(tài)代碼塊
父類-構(gòu)造方法
子類-非靜態(tài)代碼塊
子類-構(gòu)造方法
看到這观蜗,就知道初始化子類會先初始化父類。順序為 父類靜態(tài)——》子類靜態(tài)——》父類非靜態(tài)代碼塊——》父類構(gòu)造方法——》子類非靜態(tài)代碼塊——》子類構(gòu)造方法赡勘。
以上根本原因:初始化一個類的時候嫂便,如果他的父親還沒有被初始化,則先去初始化其父親闸与。
Android中類加載器介紹
android中的類加載器中主要包括三類BootClassLoader毙替,PathClassLoader和DexClassLoader。
BootClassLoader主要用于加載系統(tǒng)的類践樱,包括java和android系統(tǒng)的類庫厂画。
PathClassLoader主要用于加載應(yīng)用內(nèi)中的類。路徑是固定的拷邢,只能加載
/data/app中的apk袱院,無法指定解壓釋放dex的路徑。所以PathClassLoader是無法實現(xiàn)動態(tài)加載的。
DexClassLoader可以用于加載任意路徑的zip,jar或者apk文件忽洛∧寤荩可以實現(xiàn)動態(tài)加載。下面來具體看看應(yīng)用程序中的類加載器欲虚。
Android的BootClassLoader和Java的BootStrapClassLoader區(qū)別:
Android虛擬機(jī)中BootClassLoader是ClassLoader內(nèi)部類绷跑,由java代碼實現(xiàn)而不是c++實現(xiàn)互艾,是Android平臺上所有ClassLoader的最終parent,這個內(nèi)部類是包內(nèi)可見,所以我們沒法使用。
Java虛擬機(jī)中BootStrapClassLoader是由原生代碼(C++)編寫的轰驳,負(fù)責(zé)加載java核心類庫(例如rt.jar等) .
補充知識點:
Log.i("ljj", "Context的類加載器:"+ Context.class.getClassLoader());
Log.i("ljj", "TextView的類加載器: "+ TextView.class.getClassLoader());
//打印結(jié)果
02-14 12:37:49.161 22341-22341/com.ljj.host I/ljj: Context的類加載器:java.lang.BootClassLoader@a645091
02-14 12:37:49.162 22341-22341/com.ljj.host I/ljj: TextView的類加載器: java.lang.BootClassLoader@a645091
可見系統(tǒng)的類都是由BootClassLoader加載完成茸歧。
Log.i("ljj", "classLoader:"+getClassLoader());
02-14 13:19:23.730 20518-20518/com.ljj.host I/ljj: classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.ljj.host-2/base.apk"],nativeLibraryDirectories=[/data/app/com.ljj.host-2/lib/arm64, /vendor/lib64, /system/lib64]]]
DexClassLoader:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
DexClassLoader的源碼很簡單诊胞,只包含一個構(gòu)造函數(shù)燥筷,看來所有的工作都是在BaseDexClassLoader中完成的。這里再看BaseDexClassLoader前锈锤,先說一下DexClassLoader構(gòu)造函數(shù)的四個參數(shù)驯鳖。
- dexPath:是加載apk/dex/jar的路徑
- optimizedDirectory:是dex的輸出路徑(因為加載apk/jar的時候會解壓除dex文件,這個路徑就是保存dex文件的)
- librarySearchPath:是加載的時候需要用到的lib庫牙咏,這個一般不用臼隔,可以傳入Null
- parent:給DexClassLoader指定父加載器
下面繼續(xù)分析BaseClassLoader。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
可以看出妄壶,DexClassLoader會通過傳入的路徑構(gòu)造出一個DexPathList對象摔握,作為pathList。從findClass方法可以看出來加載的類都是從pathList中查找丁寄。至于DexPathList對象的源碼就不往下具體分析了氨淌,簡單的理解就是將每個dex都構(gòu)建成Element元素,放入到dexElements數(shù)組中伊磺,多說一句盛正,這個dexElements數(shù)組的用處很大,MultiDex方案以及由此衍生出的QQ空間熱更新方案都是通過改變dexElements數(shù)組的元素位置來實現(xiàn)的屑埋。感興趣可以參考:Android類加載器分析
Android類加載器和Java的類加載器工作機(jī)制是類似的豪筝,使用雙親委托機(jī)制。
參考:
- 深入理解Java虛擬機(jī) 周志明 著
- Android類加載器分析
- Java面試相關(guān)(一)-- Java類加載全過程
- Android類加載器以及與Java類加載器區(qū)別
- Java類加載
- 自己Demo代碼
- 極客時間APP核心技術(shù)第23講|請介紹類加載過程摘能,什么是雙親委派模型续崖?
聲明:此為原創(chuàng),轉(zhuǎn)載請聯(lián)系作者
作者:微信公眾號添加公眾號-遛狗的程序員 团搞,或者可以掃描以下二維碼關(guān)注相關(guān)技術(shù)文章严望。
當(dāng)然喜愛技術(shù),樂于分享的你也可以可以添加作者微信號: