(轉(zhuǎn)載)Java 類加載機(jī)制(阿里面試題)-何時(shí)初始化類

原文鏈接: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ī)制

參考:Java虛擬機(jī)類加載機(jī)制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雨涛,隨后出現(xiàn)的幾起案子枢舶,更是在濱河造成了極大的恐慌,老刑警劉巖替久,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凉泄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蚯根,警方通過(guò)查閱死者的電腦和手機(jī)后众,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颅拦,“玉大人蒂誉,你說(shuō)我怎么就攤上這事【嗨В” “怎么了右锨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)锥债。 經(jīng)常有香客問(wèn)我陡蝇,道長(zhǎng)迫卢,這世上最難降的妖魔是什么该溯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮香府,結(jié)果婚禮上允趟,老公的妹妹穿的比我還像新娘恼策。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布涣楷。 她就那樣靜靜地躺著分唾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狮斗。 梳的紋絲不亂的頭發(fā)上绽乔,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音碳褒,去河邊找鬼折砸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛沙峻,可吹牛的內(nèi)容都是我干的睦授。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼摔寨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼去枷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起是复,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤删顶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后佑笋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體翼闹,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蒋纬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了猎荠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜀备,死狀恐怖关摇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碾阁,我是刑警寧澤输虱,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站脂凶,受9級(jí)特大地震影響宪睹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚕钦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一亭病、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘶居,春花似錦罪帖、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)菠齿。三九已至,卻和暖如春坐昙,著一層夾襖步出監(jiān)牢的瞬間绳匀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工炸客, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留襟士,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓嚷量,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逆趣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝶溶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • C/C++在運(yùn)行前需要完成預(yù)處理、編譯宣渗、匯編抖所、鏈接;而在Java中痕囱,類加載(加載田轧、連接、初始化)是在程序運(yùn)行期間第...
    Steven1997閱讀 967評(píng)論 1 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法鞍恢,類相關(guān)的語(yǔ)法傻粘,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法帮掉,異常的語(yǔ)法弦悉,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,639評(píng)論 18 399
  • 虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存, 并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)蟆炊、轉(zhuǎn)換解析和初始化稽莉, 最終形成可以被虛擬機(jī)直接使...
    好好學(xué)習(xí)Sun閱讀 1,205評(píng)論 0 3
  • 昨夜的星空, 不在眼前涩搓, 在過(guò)去污秆。 今晨的光線, 不在未來(lái)昧甘, 在當(dāng)下良拼。 你從昨夜走向今晨, 得到了什么疾层, 又失去了...
    小劇在成長(zhǎng)閱讀 251評(píng)論 4 6
  • 今天上午去買了一大包零食将饺,為出去游玩給自己和孩子準(zhǔn)備的。 每次出游我都會(huì)在背包里裝滿各種零食:樸素可愛(ài)的棒棒糖,五...
    陽(yáng)光與茶閱讀 298評(píng)論 0 0