1- 類加載的時機(jī)
與其他語言不一樣,java的類的加載渤刃、連接和初始化都是運(yùn)行期間完成的拥峦,在初始化之前,必須要完成加載卖子、驗(yàn)證略号、準(zhǔn)備。所以說要對類進(jìn)行初始化就是要對類進(jìn)行加載的時機(jī)揪胃。
1.1- 以下為5種會觸發(fā)類初始化的情況
有且只有這5種情況會觸發(fā)類的初始化璃哟,也成為對一個類的主動引用,其他的引用類的方式都不會觸發(fā)初始化喊递,稱為被動引用。
- 創(chuàng)建類的實(shí)例阳似、也就是new一個對象的時候
- 訪問某個類或接口的靜態(tài)變量骚勘、或者對該靜態(tài)常量賦值,調(diào)用靜態(tài)方法(除了被final修飾,以及已在編譯期間把結(jié)果放入常量池的靜態(tài)字段)
- 使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時候俏讹,如果類沒有加載当宴,就會首先觸發(fā)其初始化。
- 當(dāng)初始化一個類的子類泽疆,會首先初始化子類的父類户矢。
- 當(dāng)虛擬機(jī)啟動時,用戶需要指定一個要執(zhí)行的主類(包含main方法的類)殉疼,虛擬機(jī)會首先初始化這個類梯浪。
1.2- 幾個典型的被動引用類的例子
- 通過子類引用父類的靜態(tài)字段,不會導(dǎo)致子類的初始化瓢娜,而是對父類進(jìn)行初始化
- 通過數(shù)組定義來引用類挂洛,不會觸發(fā)此類的初始化
MyClass [] a = new MyClass[10];
這將會加載數(shù)組類的初始化,并不會觸發(fā)MyClass類的加載 - 常量在編譯階段會存入調(diào)用類的常量池中眠砾,本質(zhì)上并沒有直接引用到定義常量的類虏劲,因此不會觸發(fā)定義常量的類的初始化。通過常量傳播優(yōu)化褒颈,這兩個類實(shí)際上已經(jīng)沒有任何關(guān)聯(lián)了柒巫。
這里區(qū)分一下動態(tài)加載和靜態(tài)加載,new創(chuàng)建對象時靜態(tài)加載類谷丸,因?yàn)轭惐仨氃诰幾g階段提供堡掏,forName動態(tài)加載類,編譯階段不必提供淤井。比如new一個對象布疼,則編譯的時候必須提供這個類的定義及文件在一起編譯,否則會拋出ClassNotFound的異常币狠,而用forName在進(jìn)行編譯是不用提供這個類的定義以及文件游两,而是在運(yùn)行時虛擬機(jī)去指定路徑上,比如classpath上主動搜尋這個類的定義文件(也可以從網(wǎng)絡(luò)上獲取class文件的二進(jìn)制流)漩绵。
2- 類加載的過程
將二進(jìn)制字節(jié)碼文件轉(zhuǎn)換成JVM中的Class對象贱案,初始化不是類加載時必須觸發(fā)的析既。類加載的各個階段都是按照順序開始的器联,但是在同一時間可能會出現(xiàn)多個階段混合執(zhí)行的情況。
2.1- 加載
獲取類的class文件中的二進(jìn)制數(shù)據(jù)税朴,讀到內(nèi)存中碍扔。
將其代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化成為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)瘩燥。
-
創(chuàng)建一個這個類的Class對象,作為方法去此類數(shù)據(jù)的訪問入口不同。
注意:Class對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)厉膀,并向java程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口溶耘,hotSpot虛擬機(jī)的Class對象在方法區(qū)。
數(shù)組類本身不通過類加載器創(chuàng)建服鹅,它是java虛擬機(jī)直接創(chuàng)建的凳兵,遞歸采用數(shù)組元素的類加載過程去加載這個數(shù)組元素
2.2- 驗(yàn)證
確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全(文件格式企软,元數(shù)據(jù)庐扫、字節(jié)碼、符號引用驗(yàn)證)
2.3- 準(zhǔn)備
為類變量分配內(nèi)存并設(shè)置類變量初始值(各種數(shù)據(jù)類型的零值)的階段仗哨,這些內(nèi)存將在方法區(qū)中進(jìn)行分配形庭。但是如果類字段的字段屬性表中存在ConstantValue屬性,那在準(zhǔn)備階段變量值就會初始化為ConstantValue屬性指定的值藻治。
2.4- 解析
- 將常量池內(nèi)的符號引用替換成直接引用(類或接口碘勉,字段,方法等)
- 符號引用:符號引用以一組符號來描述所引用的目標(biāo)桩卵,符號可以是任何形式的字面常量验靡,只要使用時能無歧義地定位到目標(biāo)即可。符號引用于虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān)雏节,引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中胜嗓。
- 直接引用:直接引用可以是直接指向目標(biāo)的指針、相對偏移量或者一個能間接定位到目標(biāo)的句柄钩乍。如果有了直接引用辞州,那引用目標(biāo)必定存在與內(nèi)存中。
2.5- 類的初始化
執(zhí)行類構(gòu)造器<clinit>()方法的過程:
<clinit>()是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的寥粹,不需要先初始化父類構(gòu)造器变过,也非必須。
2.6- 類加載器(雙親委派模型)
- Bootstrap ClassLoader
負(fù)責(zé)加載$JAVA_HOME中jre/lib/rt.jar里所有的class涝涤,由C++實(shí)現(xiàn)媚狰,不是ClassLoader子類,是虛擬機(jī)的一部分阔拳,HotSpot啟動時會初始化此ClassLoader崭孤,開發(fā)者無法直接獲取啟動類加載器的引用,所以不允許直接通過引用進(jìn)行操作糊肠。其他的ClassLoader都是由java實(shí)現(xiàn)的辨宠,獨(dú)立于虛擬機(jī)外部,并且全部繼承自抽象類java.lang.ClassLoader货裹。當(dāng)運(yùn)行一個程序時嗤形,JVM啟動,運(yùn)行Bootstrap ClassLoader弧圆,該ClassLoader加載Java核心API(Extension ClassLoader派殷、App ClassLoader包含在內(nèi)还最,也被加載)墓阀,然后調(diào)用Extension ClassLoader加載拓展API毡惜,最后調(diào)用App ClassLoader加載CLASSPATH目錄下定義的Class。
- Extension ClassLoader
負(fù)責(zé)加載java平臺中擴(kuò)展功能的一些jar包斯撮,包括$JAVA_HOME中jre/lib/*.jar或者被java.ext.dir系統(tǒng)變量所指定的路徑中的所有類庫经伙。
- App ClassLoader
負(fù)責(zé)加載classpath中指定的jar包目錄中的class,被稱為系統(tǒng)加載器勿锅,是ClassLoader.getSystemClassLoader()方法的返回值帕膜,如果用戶沒有自定義過自己的類加載器,一般情況下這就是程序中默認(rèn)的類加載器溢十。AppClassloader加載器的父加載器是ExtClassloader
- Custom ClassLoader
屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader垮刹,如tomcat,jboss都會根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader张弛;用戶自定義的無參加載器的父類加載器默認(rèn)是AppClassloader加載器
雙親委派模型
委派過程:如果一個類加載器收到類加載的請求荒典,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成吞鸭,每一層次的類加載器都是如此寺董,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有父類加載器反饋?zhàn)约簾o法完成這個加載請求刻剥,搜索范圍內(nèi)沒有找到所需的類時遮咖,子加載器才會嘗試自己加載。
雙親委派模型使得類具有帶有優(yōu)先級的層次關(guān)系造虏,避免同一個類在不同類加載器環(huán)境中發(fā)生混亂御吞,導(dǎo)致出現(xiàn)不同類加載器都加載一個類的情況。比如java.lang.Object存在于啟動類的搜索路徑上漓藕,所以無論哪個類加載器要加載這個類最終都會委派給啟動類加載器陶珠,因此Object類在程序的各種類加載器環(huán)境下都是同一個類。
比較兩個類是否相等:
由同一個類加載器加載撵术,類的全限定名相等
其中相等的含義是指:通過** equals()背率、isAssignableFrom()、isInstance()嫩与、instanceof關(guān)鍵字 **做對象所屬關(guān)系判定情況寝姿。
package jvm;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by zhanglbjames@163.com on 2017/4/11.
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
ClassLoader myLoader = new ClassLoader(){
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException{
try{
String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream in = getClass().getResourceAsStream(fileName);
if (in == null){
return super.loadClass(name);
}
byte[] b = new byte[in.available()];
in.read(b);
return defineClass(name,b,0,b.length);
}catch (IOException e){
throw new ClassNotFoundException();
}
}
};
// 由自定義加載器加載的
Object obj = myLoader.loadClass("jvm.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
// jvm.ClassLoaderTest位于classpath上,所以App ClassLoader會加載這個類
// 所以會存在兩個由不同加載器加載進(jìn)來的jvm.ClassLoaderTest划滋,進(jìn)行判別返回false
System.out.println(obj instanceof jvm.ClassLoaderTest);
}
}
/*
* output
*
* class jvm.ClassLoaderTest
* false
*
* */
這個自定義的類加載器覆寫了loadClass方法饵筑,方法內(nèi)沒有遵循類加載委派的模型規(guī)范,而是首先自己加載处坪,找不到則由父類加載器進(jìn)行加載根资。
一種特殊的類加載器Thread Context ClassLoader(線程上下文加載器)
使用Thread Context ClassLoader能破壞雙親委派的類加載機(jī)制架专,被廣泛用于JNDI,用來對資源進(jìn)行集中管理和查找玄帕,解決雙親委派自身模型的缺陷(用戶代碼可以調(diào)用基礎(chǔ)API部脚,但是基礎(chǔ)API無法調(diào)用用戶代碼)
先獲取線程上下文加載器,然后指定自定義的類加載器裤纹,然后就會使用用戶自定義類加載器加載指定類委刘,此時不會委派給父類加載器。