原文鏈接:Java 類加載機(jī)制(阿里面試題)-何時(shí)初始化類 - aspirant - 博客園
閱讀目錄
什么是類加載器
類加載器與類的”相同“判斷
類加載器種類
雙親委派模型
類加載過(guò)程
自定義類加載器
JAVA熱部署實(shí)現(xiàn)
什么是類加載器
負(fù)責(zé)讀取 Java 字節(jié)代碼隘谣,并轉(zhuǎn)換成java.lang.Class類的一個(gè)實(shí)例;
類加載器與類的”相同“判斷
類加載器除了用于加載類外痢站,還可用于確定類在Java虛擬機(jī)中的唯一性嫩舟。
即便是同樣的字節(jié)代碼掰吕,被不同的類加載器加載之后所得到的類,也是不同的赏陵。
通俗一點(diǎn)來(lái)講,要判斷兩個(gè)類是否“相同”伙菊,前提是這兩個(gè)類必須被同一個(gè)類加載器加載,否則這個(gè)兩個(gè)類不“相同”乳蓄。
這里指的“相同”,包括類的Class對(duì)象的equals()方法涧郊、isAssignableFrom()方法幌陕、isInstance()方法搏熄、instanceof關(guān)鍵字等判斷出來(lái)的結(jié)果。
類加載器種類
啟動(dòng)類加載器肃续,Bootstrap ClassLoader鸠澈,加載JACA_HOME\lib,或者被-Xbootclasspath參數(shù)限定的類
擴(kuò)展類加載器截驮,Extension ClassLoader笑陈,加載\lib\ext,或者被java.ext.dirs系統(tǒng)變量指定的類
應(yīng)用程序類加載器葵袭,Application ClassLoader涵妥,加載ClassPath中的類庫(kù)
自定義類加載器,通過(guò)繼承ClassLoader實(shí)現(xiàn)眶熬,一般是加載我們的自定義類
雙親委派模型
類加載器 Java 類如同其它的 Java 類一樣妹笆,也是要由類加載器來(lái)加載的;除了啟動(dòng)類加載器娜氏,每個(gè)類都有其父類加載器(父子關(guān)系由組合(不是繼承)來(lái)實(shí)現(xiàn))拳缠;
所謂雙親委派是指每次收到類加載請(qǐng)求時(shí),先將請(qǐng)求委派給父類加載器完成(所有加載請(qǐng)求最終會(huì)委派到頂層的Bootstrap ClassLoader加載器中)贸弥,如果父類加載器無(wú)法完成這個(gè)加載(該加載器的搜索范圍中沒(méi)有找到對(duì)應(yīng)的類)窟坐,子類嘗試自己加載。
雙親委派好處
避免同一個(gè)類被多次加載;
每個(gè)加載器只能加載自己范圍內(nèi)的類哲鸳;
類加載過(guò)程
類加載分為三個(gè)步驟:加載臣疑,連接,初始化徙菠;
如下圖 , 是一個(gè)類從加載到使用及卸載的全部生命周期讯沈,圖片來(lái)自參考資料;
加載
根據(jù)一個(gè)類的全限定名(如cn.edu.hdu.test.HelloWorld.class)來(lái)讀取此類的二進(jìn)制字節(jié)流到JVM內(nèi)部;
將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)(hotspot選擇將Class對(duì)象存儲(chǔ)在方法區(qū)中婿奔,Java虛擬機(jī)規(guī)范并沒(méi)有明確要求一定要存儲(chǔ)在方法區(qū)或堆區(qū)中)
轉(zhuǎn)換為一個(gè)與目標(biāo)類型對(duì)應(yīng)的java.lang.Class對(duì)象缺狠;
連接
驗(yàn)證
驗(yàn)證階段主要包括四個(gè)檢驗(yàn)過(guò)程:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證萍摊、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證;
準(zhǔn)備
為類中的所有靜態(tài)變量分配內(nèi)存空間挤茄,并為其設(shè)置一個(gè)初始值(由于還沒(méi)有產(chǎn)生對(duì)象,實(shí)例變量將不再此操作范圍內(nèi))冰木;
解析
將常量池中所有的符號(hào)引用轉(zhuǎn)為直接引用(得到類或者字段穷劈、方法在內(nèi)存中的指針或者偏移量,以便直接調(diào)用該方法)踊沸。這個(gè)階段可以在初始化之后再執(zhí)行歇终。
初始化
? 在連接的準(zhǔn)備階段,類變量已賦過(guò)一次系統(tǒng)要求的初始值逼龟,而在初始化階段练湿,則是根據(jù)程序員自己寫(xiě)的邏輯去初始化類變量和其他資源,舉個(gè)例子如下:
public static int value1 = 5;
public static int value2? = 6;
static{
? ? value2 = 66;
}
在準(zhǔn)備階段value1和value2都等于0审轮;
在初始化階段value1和value2分別等于5和66;
* 所有類變量初始化語(yǔ)句和靜態(tài)代碼塊都會(huì)在編譯時(shí)被前端編譯器放在收集器里頭辽俗,存放到一個(gè)特殊的方法中疾渣,這個(gè)方法就是方法,即類/接口初始化方法崖飘,該方法只能在類加載的過(guò)程中由JVM調(diào)用榴捡;
* 編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量朱浴;
* 如果超類還沒(méi)有被初始化吊圾,那么優(yōu)先對(duì)超類初始化,但在方法內(nèi)部不會(huì)顯示調(diào)用超類的方法翰蠢,由JVM負(fù)責(zé)保證一個(gè)類的方法執(zhí)行之前项乒,它的超類方法已經(jīng)被執(zhí)行。
* JVM必須確保一個(gè)類在初始化的過(guò)程中梁沧,如果是多線程需要同時(shí)初始化它檀何,僅僅只能允許其中一個(gè)線程對(duì)其執(zhí)行初始化操作,其余線程必須等待,只有在活動(dòng)線程執(zhí)行完對(duì)類的初始化操作之后频鉴,才會(huì)通知正在等待的其他線程栓辜。(所以可以利用靜態(tài)內(nèi)部類實(shí)現(xiàn)線程安全的單例模式)
* ?如果一個(gè)類沒(méi)有聲明任何的類變量,也沒(méi)有靜態(tài)代碼塊垛孔,那么可以沒(méi)有類方法藕甩;
何時(shí)觸發(fā)初始化
1、為一個(gè)類型創(chuàng)建一個(gè)新的對(duì)象實(shí)例時(shí)(比如new周荐、反射狭莱、序列化)
2、調(diào)用一個(gè)類型的靜態(tài)方法時(shí)(即在字節(jié)碼中執(zhí)行invokestatic指令)
3羡藐、調(diào)用一個(gè)類型或接口的靜態(tài)字段贩毕,或者對(duì)這些靜態(tài)字段執(zhí)行賦值操作時(shí)(即在字節(jié)碼中,執(zhí)行g(shù)etstatic或者putstatic指令)仆嗦,不過(guò)用final修飾的靜態(tài)字段除外辉阶,它被初始化為一個(gè)編譯時(shí)常量表達(dá)式
4、調(diào)用JavaAPI中的反射方法時(shí)(比如調(diào)用java.lang.Class中的方法瘩扼,或者java.lang.reflect包中其他類的方法)
5谆甜、初始化一個(gè)類的派生類時(shí)(Java虛擬機(jī)規(guī)范明確要求初始化一個(gè)類時(shí),它的超類必須提前完成初始化操作集绰,接口例外)
6规辱、JVM啟動(dòng)包含main方法的啟動(dòng)類時(shí)。
自定義類加載器
?要?jiǎng)?chuàng)建用戶自己的類加載器栽燕,只需要繼承java.lang.ClassLoader類罕袋,然后覆蓋它的findClass(String name)方法即可,即指明如何獲取類的字節(jié)碼流碍岔。
如果要符合雙親委派規(guī)范浴讯,則重寫(xiě)findClass方法(用戶自定義類加載邏輯);要破壞的話蔼啦,重寫(xiě)loadClass方法(雙親委派的具體邏輯實(shí)現(xiàn))榆纽。
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
class TestClassLoad {
@Override
public String toString() {
return "類加載成功。";
}
}
public class PathClassLoader extends ClassLoader {
private String classPath;
public PathClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class findClass(String name) throws ClassNotFoundException {
byte[] classData = getData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getData(String className) {
String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
try {
InputStream is = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while ((num = is.read(buffer)) != -1) {
stream.write(buffer, 0, num);
}
return stream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader pcl = new PathClassLoader("D:\\ProgramFiles\\eclipseNew\\workspace\\cp-lib\\bin");
Class c = pcl.loadClass("classloader.TestClassLoad");//注意要包括包名 System.out.println(c.newInstance());//打印類加載成功.
}
}
JAVA熱部署實(shí)現(xiàn)
首先談一下何為熱部署(hotswap)捏肢,熱部署是在不重啟 Java 虛擬機(jī)的前提下奈籽,能自動(dòng)偵測(cè)到 class 文件的變化,更新運(yùn)行時(shí) class 的行為鸵赫。Java 類是通過(guò) Java 虛擬機(jī)加載的衣屏,某個(gè)類的 class 文件在被 classloader 加載后,會(huì)生成對(duì)應(yīng)的 Class 對(duì)象奉瘤,之后就可以創(chuàng)建該類的實(shí)例勾拉。默認(rèn)的虛擬機(jī)行為只會(huì)在啟動(dòng)時(shí)加載類煮甥,如果后期有一個(gè)類需要更新的話,單純替換編譯的 class 文件藕赞,Java 虛擬機(jī)是不會(huì)更新正在運(yùn)行的 class成肘。如果要實(shí)現(xiàn)熱部署,最根本的方式是修改虛擬機(jī)的源代碼斧蜕,改變 classloader 的加載行為双霍,使虛擬機(jī)能監(jiān)聽(tīng) class 文件的更新,重新加載 class 文件批销,這樣的行為破壞性很大洒闸,為后續(xù)的 JVM 升級(jí)埋下了一個(gè)大坑。
另一種友好的方法是創(chuàng)建自己的 classloader 來(lái)加載需要監(jiān)聽(tīng)的 class均芽,這樣就能控制類加載的時(shí)機(jī)丘逸,從而實(shí)現(xiàn)熱部署。
熱部署步驟:
1掀宋、銷毀自定義classloader(被該加載器加載的class也會(huì)自動(dòng)卸載)深纲;
2、更新class
3劲妙、使用新的ClassLoader去加載class?
JVM中的Class只有滿足以下三個(gè)條件湃鹊,才能被GC回收,也就是該Class被卸載(unload):
- 該類所有的實(shí)例都已經(jīng)被GC镣奋,也就是JVM中不存在該Class的任何實(shí)例币呵。
- 加載該類的ClassLoader已經(jīng)被GC。
- 該類的java.lang.Class 對(duì)象沒(méi)有在任何地方被引用侨颈,如不能在任何地方通過(guò)反射訪問(wèn)該類的方法余赢。
延伸出來(lái)問(wèn)題進(jìn)行分析:
看到這個(gè)題目,很多人會(huì)覺(jué)得我寫(xiě)我的java代碼哈垢,至于類没佑,JVM愛(ài)怎么加載就怎么加載,博主有很長(zhǎng)一段時(shí)間也是這么認(rèn)為的温赔。隨著編程經(jīng)驗(yàn)的日積月累,越來(lái)越感覺(jué)到了解虛擬機(jī)相關(guān)要領(lǐng)的重要性鬼癣。閑話不多說(shuō)陶贼,老規(guī)矩,先來(lái)一段代碼吊吊胃口待秃。
public class SSClass
{
? ? static
? ? {
? ? ? ? System.out.println("SSClass");
? ? }
}?
public class SuperClass extends SSClass
{
? ? static
? ? {
? ? ? ? System.out.println("SuperClass init!");
? ? }
? ? public static int value = 123;
? ? public SuperClass()
? ? {
? ? ? ? System.out.println("init SuperClass");
? ? }
}
public class SubClass extends SuperClass
{
? ? static
? ? {
? ? ? ? System.out.println("SubClass init");
? ? }
? ? static int a;
? ? public SubClass()
? ? {
? ? ? ? System.out.println("init SubClass");
? ? }
}
public class NotInitialization
{
? ? public static void main(String[] args)
? ? {
? ? ? ? System.out.println(SubClass.value);
? ? }
}
運(yùn)行結(jié)果:
SSClass
SuperClass init!123
答案答對(duì)了嚢菅怼?
也許有人會(huì)疑問(wèn):為什么沒(méi)有輸出SubClass init章郁。ok~解釋一下:對(duì)于靜態(tài)字段枉氮,只有直接定義這個(gè)字段的類才會(huì)被初始化志衍,因此通過(guò)其子類來(lái)引用父類中定義的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化而不會(huì)觸發(fā)子類的初始化聊替。
上面就牽涉到了虛擬機(jī)類加載機(jī)制楼肪。如果有興趣,可以繼續(xù)看下去惹悄。
類加載過(guò)程
類從被加載到虛擬機(jī)內(nèi)存中開(kāi)始春叫,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載(Loading)泣港、驗(yàn)證(Verification)暂殖、準(zhǔn)備(Preparation)、解析(Resolution)当纱、初始化(Initialization)呛每、使用(Using)和卸載(Unloading)7個(gè)階段。其中準(zhǔn)備坡氯、驗(yàn)證晨横、解析3個(gè)部分統(tǒng)稱為連接(Linking)。如圖所示廉沮。
加載颓遏、驗(yàn)證、準(zhǔn)備滞时、初始化和卸載這5個(gè)階段的順序是確定的叁幢,類的加載過(guò)程必須按照這種順序按部就班地開(kāi)始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開(kāi)始坪稽,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定(也稱為動(dòng)態(tài)綁定或晚期綁定)曼玩。以下陳述的內(nèi)容都已HotSpot為基準(zhǔn)。
加載
在加載階段(可以參考java.lang.ClassLoader的loadClass()方法)窒百,虛擬機(jī)需要完成以下3件事情:
通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流(并沒(méi)有指明要從一個(gè)Class文件中獲取黍判,可以從其他渠道,譬如:網(wǎng)絡(luò)篙梢、動(dòng)態(tài)生成顷帖、數(shù)據(jù)庫(kù)等);
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)渤滞;
在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象贬墩,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口;
加載階段和連接階段(Linking)的部分內(nèi)容(如一部分字節(jié)碼文件格式驗(yàn)證動(dòng)作)是交叉進(jìn)行的妄呕,加載階段尚未完成陶舞,連接階段可能已經(jīng)開(kāi)始,但這些夾在加載階段之中進(jìn)行的動(dòng)作绪励,仍然屬于連接階段的內(nèi)容肿孵,這兩個(gè)階段的開(kāi)始時(shí)間仍然保持著固定的先后順序唠粥。
驗(yàn)證
驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求停做,并且不會(huì)危害虛擬機(jī)自身的安全晤愧。
驗(yàn)證階段大致會(huì)完成4個(gè)階段的檢驗(yàn)動(dòng)作:
文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范;例如:是否以魔術(shù)0xCAFEBABE開(kāi)頭雅宾、主次版本號(hào)是否在當(dāng)前虛擬機(jī)的處理范圍之內(nèi)养涮、常量池中的常量是否有不被支持的類型。
元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析(注意:對(duì)比javac編譯階段的語(yǔ)義分析)眉抬,以保證其描述的信息符合Java語(yǔ)言規(guī)范的要求贯吓;例如:這個(gè)類是否有父類,除了java.lang.Object之外蜀变。
字節(jié)碼驗(yàn)證:通過(guò)數(shù)據(jù)流和控制流分析悄谐,確定程序語(yǔ)義是合法的、符合邏輯的库北。
符號(hào)引用驗(yàn)證:確保解析動(dòng)作能正確執(zhí)行爬舰。
驗(yàn)證階段是非常重要的,但不是必須的寒瓦,它對(duì)程序運(yùn)行期沒(méi)有影響情屹,如果所引用的類經(jīng)過(guò)反復(fù)驗(yàn)證,那么可以考慮采用-Xverifynone參數(shù)來(lái)關(guān)閉大部分的類驗(yàn)證措施杂腰,以縮短虛擬機(jī)類加載的時(shí)間垃你。
準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配喂很。這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量)惜颇,而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在堆中少辣。其次凌摄,這里所說(shuō)的初始值“通常情況”下是數(shù)據(jù)類型的零值,假設(shè)一個(gè)類變量的定義為:
public static int value = 123;
那變量value在準(zhǔn)備階段過(guò)后的初始值為0而不是123.因?yàn)檫@時(shí)候尚未開(kāi)始執(zhí)行任何java方法漓帅,而把value賦值為123的putstatic指令是程序被編譯后锨亏,存放于類構(gòu)造器()方法之中,所以把value賦值為123的動(dòng)作將在初始化階段才會(huì)執(zhí)行忙干。
至于“特殊情況”是指:public static final int value=123屯伞,即當(dāng)類字段的字段屬性是ConstantValue時(shí),會(huì)在準(zhǔn)備階段初始化為指定的值豪直,所以標(biāo)注為final之后,value的值在準(zhǔn)備階段初始化為123而非0珠移。
解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程弓乙。解析動(dòng)作主要針對(duì)類或接口末融、字段、類方法暇韧、接口方法勾习、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用進(jìn)行懈玻。
初始化
類初始化階段是類加載過(guò)程的最后一步巧婶,到了初始化階段,才真正開(kāi)始執(zhí)行類中定義的java程序代碼涂乌。在準(zhǔn)備極端艺栈,變量已經(jīng)付過(guò)一次系統(tǒng)要求的初始值,而在初始化階段湾盒,則根據(jù)程序猿通過(guò)程序制定的主管計(jì)劃去初始化類變量和其他資源湿右,或者說(shuō):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程。
<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊static{}中的語(yǔ)句合并產(chǎn)生的罚勾,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的毅人,靜態(tài)語(yǔ)句塊只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在它之后的變量尖殃,在前面的靜態(tài)語(yǔ)句塊可以賦值丈莺,但是不能訪問(wèn)。如下:
public class Test
{
? ? static
? ? {
? ? ? ? i=0;
? ? ? ? System.out.println(i);//這句編譯器會(huì)報(bào)錯(cuò):Cannot reference a field before it is defined(非法向前應(yīng)用)
? ? }
? ? static int i=1;
}
那么去掉報(bào)錯(cuò)的那句送丰,改成下面:
public class Test
{
? ? static
? ? {
? ? ? ? i=0;
//? ? ? System.out.println(i);
? ? }
? ? static int i=1;
? ? public static void main(String args[])
? ? {
? ? ? ? System.out.println(i);
? ? }
}
輸出結(jié)果是什么呢缔俄?當(dāng)然是1啦~在準(zhǔn)備階段我們知道i=0,然后類初始化階段按照順序執(zhí)行蚪战,首先執(zhí)行static塊中的i=0,接著執(zhí)行static賦值操作i=1,最后在main方法中獲取i的值為1牵现。
<clinit>()方法與實(shí)例構(gòu)造器<init>()方法不同,它不需要顯示地調(diào)用父類構(gòu)造器邀桑,虛擬機(jī)會(huì)保證在子類<clinit>()方法執(zhí)行之前瞎疼,父類的<clinit>()方法方法已經(jīng)執(zhí)行完畢,回到本文開(kāi)篇的舉例代碼中壁畸,結(jié)果會(huì)打印輸出:SSClass就是這個(gè)道理贼急。
由于父類的<clinit>()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作捏萍。
<clinit>()方法對(duì)于類或者接口來(lái)說(shuō)并不是必需的太抓,如果一個(gè)類中沒(méi)有靜態(tài)語(yǔ)句塊,也沒(méi)有對(duì)變量的賦值操作令杈,那么編譯器可以不為這個(gè)類生產(chǎn)<clinit>()方法走敌。
接口中不能使用靜態(tài)語(yǔ)句塊,但仍然有變量初始化的賦值操作逗噩,因此接口與類一樣都會(huì)生成<clinit>()方法掉丽。但接口與類不同的是跌榔,執(zhí)行接口的()方法不需要先執(zhí)行父接口的<clinit>()方法。只有當(dāng)父接口中定義的變量使用時(shí)捶障,父接口才會(huì)初始化僧须。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>()方法项炼。
虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確的加鎖担平、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類锭部,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的()方法暂论,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢空免。如果在一個(gè)類的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作空另,就可能造成多個(gè)線程阻塞,在實(shí)際應(yīng)用中這種阻塞往往是隱藏的蹋砚。
package jvm.classload;
public class DealLoopTest
{
? ? static class DeadLoopClass
? ? {
? ? ? ? static
? ? ? ? {
? ? ? ? ? ? if(true)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread()+"init DeadLoopClass");
? ? ? ? ? ? ? ? while(true)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? public static void main(String[] args)
? ? {
? ? ? ? Runnable script = new Runnable(){
? ? ? ? ? ? public void run()
? ? ? ? ? ? {
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread()+" start");
? ? ? ? ? ? ? ? DeadLoopClass dlc = new DeadLoopClass();
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread()+" run over");
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread thread1 = new Thread(script);
? ? ? ? Thread thread2 = new Thread(script);
? ? ? ? thread1.start();
? ? ? ? thread2.start();
? ? }
}
運(yùn)行結(jié)果:(即一條線程在死循環(huán)以模擬長(zhǎng)時(shí)間操作扼菠,另一條線程在阻塞等待)
Thread[Thread-0,5,main] start
Thread[Thread-1,5,main] start
Thread[Thread-0,5,main]init DeadLoopClass
需要注意的是,其他線程雖然會(huì)被阻塞坝咐,但如果執(zhí)行()方法的那條線程退出()方法后循榆,其他線程喚醒之后不會(huì)再次進(jìn)入()方法。同一個(gè)類加載器下墨坚,一個(gè)類型只會(huì)初始化一次秧饮。
將上面代碼中的靜態(tài)塊替換如下:
static
{
? ? System.out.println(Thread.currentThread() + "init DeadLoopClass");
? ? try
? ? {
? ? ? ? TimeUnit.SECONDS.sleep(10);
? ? }
? ? catch (InterruptedException e)
? ? {
? ? ? ? e.printStackTrace();
? ? }
}
運(yùn)行結(jié)果:
Thread[Thread-0,5,main] start
Thread[Thread-1,5,main] start
Thread[Thread-1,5,main]init DeadLoopClass (之后sleep 10s)
Thread[Thread-1,5,main] run over
Thread[Thread-0,5,main] run over
虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有5中情況(jdk1.7)必須對(duì)類進(jìn)行“初始化”(而加載、驗(yàn)證泽篮、準(zhǔn)備自然需要在此之前開(kāi)始):
遇到new,getstatic,putstatic,invokestatic這失調(diào)字節(jié)碼指令時(shí)盗尸,如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化帽撑。生成這4條指令的最常見(jiàn)的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候泼各、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候亏拉,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候扣蜻。
使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)有進(jìn)行過(guò)初始化及塘,則需要先觸發(fā)其初始化莽使。
當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化笙僚,則需要先觸發(fā)其父類的初始化芳肌。
當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類亿笤。
當(dāng)使用jdk1.7動(dòng)態(tài)語(yǔ)言支持時(shí)檬嘀,如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行初始化责嚷,則需要先出觸發(fā)其初始化。
開(kāi)篇已經(jīng)舉了一個(gè)范例:通過(guò)子類引用付了的靜態(tài)字段掂铐,不會(huì)導(dǎo)致子類初始化罕拂。
這里再舉兩個(gè)例子。
1全陨、通過(guò)數(shù)組定義來(lái)引用類爆班,不會(huì)觸發(fā)此類的初始化:(SuperClass類已在本文開(kāi)篇定義)
public class NotInitialization
{
? ? public static void main(String[] args)
? ? {
? ? ? ? SuperClass[] sca = new SuperClass[10];
? ? }
}
運(yùn)行結(jié)果:(無(wú))
2、常量在編譯階段會(huì)存入調(diào)用類的常量池中辱姨,本質(zhì)上并沒(méi)有直接引用到定義常量的類柿菩,因此不會(huì)觸發(fā)定義常量的類的初始化:
public class ConstClass
{
? ? static
? ? {
? ? ? ? System.out.println("ConstClass init!");
? ? }
? ? public static? final String HELLOWORLD = "hello world";
}
public class NotInitialization
{
? ? public static void main(String[] args)
? ? {
? ? ? ? System.out.println(ConstClass.HELLOWORLD);
? ? }
}
運(yùn)行結(jié)果:
hello world
參考:類加載機(jī)制