類的加載過程概況
類從被加載到虛擬機內存開始,直到卸載出內存已慢,它的生命周期包含了:加載曲聂,驗證,準備佑惠,解析朋腋,初始化,使用和卸載7個階段膜楷。
其中旭咽,加載,驗證赌厅,準備穷绵,初始化和卸載五個階段是順序是固定的。這些階段通常都是相互交叉混合進行的特愿,不過順序不會亂仲墨。解析階段在某些情況下可以在初始化之后再開始,這是為了Java語言的運行時綁定揍障。
引發(fā)類的初始化的條件
類的初始化階段往往是真正執(zhí)行類中定義的java程序代碼的過程目养。按照程序員主觀定義去初始化類變量和其他資源。
類加載的第一個階段“”“加載”毒嫡,虛擬機本沒有強制規(guī)定混稽,但是對于初始化進行了嚴格的規(guī)定 。有且僅有以下五種情況下必須對類進行初始化:
- 使用new關鍵字實例化一個對象审胚,或者訪問(讀取和設置)類的靜態(tài)變量(被final修飾的除外匈勋,常量是一種特殊的變量,編譯器將其當做值而非域來對待膳叨,這是一種優(yōu)化)洽洁,以及調用一個類的靜態(tài)方法的時候。
- 使用java.lang.reflect包中的方法對類進行反射調用的時候菲嘴。
以下是獲取類的三種方式饿自。
- 通過類名.class方式Class<?> cType = ClassName.class
- 通過Class.forName()方法獲得,Class<?> cType = Class.forName("類全名")
- 通過對象名.getClass()方法獲取龄坪,Class<?> cType = objName.getClass();
區(qū)別:第一種方式不執(zhí)行靜態(tài)塊和動態(tài)構造塊昭雌。第二種方式是執(zhí)行靜態(tài)塊不執(zhí)行動態(tài)構造塊。第三種方式執(zhí)行靜態(tài)塊和動態(tài)構造塊健田。
以下是獲取類之后利用反射創(chuàng)建對象的方式: - 通過Class對象的newInstance的方法烛卧,局限為只能調用默認的構造函數(shù)
- 通過取得Class對象的getConstructor方法取得Constructor對象,然后通過調用Constructor類的newInstance的兩個方法妓局。
- 通過Array.newInstance方法來創(chuàng)建對象,不過只適用于數(shù)組总放。
- 當初始化一個類的時候呈宇,如果發(fā)現(xiàn)其父類還沒有進行初始化,則需要觸發(fā)其父類的初始化局雄。
- 當虛擬機啟動的時候甥啄,用戶需要指定一個需要執(zhí)行的main類,虛擬機會先初始化這個主類炬搭。
以上情況稱為對一個類的主動引用蜈漓,被動引用不會引發(fā)類的初始化。
注意:接口的加載過程和類的加載過程稍有不同宫盔,接口中不能使用static{}塊迎变,當一個接口在初始化時,并不要求其父接口全部完成初始化飘言,只有真正使用帶父接口的時候(引用接口中定義的常量)才會初始化
被動引用 :1衣形、 子類調用父類的靜態(tài)變量,子類不會被初始化姿鸿,只有父類被初始化谆吴,對于靜態(tài)字段,只有直接定義此變量的類才可以被初始化苛预。
2句狼、通過數(shù)組定義來引用類,不會觸發(fā)類的初始化热某。
3腻菇、訪問類的常量,不會初始化類昔馋。
類加載過程
加載階段
預加載:虛擬機啟動自動加載JAVA_HOME/lib/rt.jar中的基礎類筹吐,可以通過-XX:+TraceClassLoading來查看輸出。
運行時加載:虛擬機用到.class文件的時候秘遏,會去內存中查看一下.class是否存在丘薛,如果沒有就按照類的全限定名來加載此類。
加載階段主要做了以下三件事情:
1邦危、獲取.class文件的二進制流
2洋侨、將字節(jié)流中的類信息、靜態(tài)變量倦蚪、字節(jié)碼希坚、常量這些.class文件中的內容放入方法區(qū)中。
3陵且、在內存中生成一個代表這個對象的java.lang.Class對象裁僧。作為此類所有數(shù)據(jù)的入口。
驗證階段
這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求。
準備階段
準備階段是正式為類變量分配內存并設置其初始值的時候锅知,內存都是被分配到方法區(qū)中播急。準備階段不分配類中的實例變量的內存脓钾,實例變量的內存將會在對象實例化的時候隨著對象一起分配到java堆中售睹。
public static int value=123;
在準備階段value的初始值為0,在初始化階段才會變?yōu)?23可训;
解析階段
解析階段是將符號引用轉化為直接引用的過程昌妹。
符號引用是用一組符號來表示所引用的類和接口的全限定名、字段的名字和類型握截、方法的名字和類型飞崖,只要使用時可以無歧義的定位即可。符號引用與虛擬機實現(xiàn)的內存布局無關谨胞,引用的目標并不一定已經(jīng)加載到內存中固歪。
直接引用是直接指向目標的指針、相對偏移量和一個能間接定位到目標的句柄胯努。直接引用與虛擬機的內存布局是相關的牢裳,有了直接引用,說明引用的目標已經(jīng)在內存中叶沛。
初始化階段
類初始化時類加載過程的最后一步蒲讯,前面的類加載過程,除了在加載階段用戶可以通過自定義類加載器參與之外灰署,其余動作完全由虛擬機主導和控制判帮。到了初始化階段,才真正執(zhí)行類中定義的java代碼溉箕。
其實初始化階段做的是就是給static變量賦予用戶指定的值和靜態(tài)執(zhí)行代碼塊晦墙。
初始化階段是執(zhí)行類構造器<clinit>()方法的過程。<clinit>()方法是由編譯器自動收集類中的類變量的賦值動作和靜態(tài)語句塊中的語句合并產(chǎn)生肴茄。
虛擬機會保證類的初始化在多線程環(huán)境中被正確地加鎖偎痛、同步,即如果多個線程同時去初始化一個類独郎,那么只會有一個類去執(zhí)行這個類的<clinit>()方法踩麦,其他線程都要阻塞等待,直至活動線程執(zhí)行<clinit>()方法完畢氓癌。因此如果在一個類的<clinit>()方法中有耗時很長的操作谓谦,就可能造成多個進程阻塞。不過其他線程雖然會阻塞贪婉,但是執(zhí)行<clinit>()方法的那條線程退出<clinit>()方法后反粥,其他線程不會再次進入<clinit>()方法了,因為同一個類加載器下,一個類只會初始化一次才顿。也就是說初始化之前的階段可以有多個線程執(zhí)行莫湘,而初始化階段只能有一個線程執(zhí)行