JVM類加載機(jī)制該如何解析,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
創(chuàng)新互聯(lián)公司長(zhǎng)期為上1000+客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為平安企業(yè)提供專業(yè)的網(wǎng)站設(shè)計(jì)制作、網(wǎng)站制作,平安網(wǎng)站改版等技術(shù)服務(wù)。擁有十載豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。
與那些在編譯時(shí)需要進(jìn)行鏈接工作的語(yǔ)言不同,在Java語(yǔ)言里,類型的加載、連接和初始化過(guò)程都是在程序運(yùn)行期間完成的,例如import java.util.*
下面包含很多類,但是,在程序運(yùn)行的時(shí)候,虛擬機(jī)只會(huì)加載哪些我們程序需要的類。這種策略雖然會(huì)令類加載時(shí)稍微增加一些性能開銷,但是會(huì)為Java應(yīng)用程序提供高度的靈活性。
類從創(chuàng)建起(這里的類也可能是接口,下同),就注定了其是有生命周期的(這里的生命周期指的是類在運(yùn)行期間所經(jīng)歷的過(guò)程,與是否存儲(chǔ)在存儲(chǔ)介質(zhì)上無(wú)關(guān))。類從被虛擬機(jī)加載到內(nèi)存中開始,到卸載出內(nèi)存為止,它的生命周期經(jīng)歷了加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading),一共七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析部分統(tǒng)稱為連接。這七個(gè)階段可以用如下圖描述:
從上圖中可以明顯看出各個(gè)階段是有順序的,加載、驗(yàn)證、準(zhǔn)備、初始化這個(gè)5個(gè)階段的順序是固定的,也就是說(shuō)類的加載過(guò)程必須按照這種順序按部就班開始;解析階段則不一定,解析階段的工作完全可能在初始化之后才開始,之所以這么設(shè)計(jì),就是為了支持Java語(yǔ)言的動(dòng)態(tài)綁定。還有一點(diǎn)需要注意的是,雖然上述的5個(gè)階段可能按照順序開始,但是并不是說(shuō)一個(gè)接一個(gè)階段完成后才開始,一個(gè)階段的進(jìn)行完全可能激活另一個(gè)階段的進(jìn)行,交叉混合式的進(jìn)行。
那么什么情況下需要開始類加載過(guò)程的第一個(gè)階段,加載到內(nèi)存中呢?這就不得不涉及兩個(gè)概念:主動(dòng)引用和被動(dòng)引用。根據(jù)Java虛擬機(jī)的規(guī)范,只有5中情況屬于主動(dòng)引用:
遇到new(使用new 關(guān)鍵字實(shí)例化一個(gè)對(duì)象)、getstatic(讀取一個(gè)類的靜態(tài)字段)、putstatic或者invokestatic(設(shè)置一個(gè)類的靜態(tài)字段)這4條指令的時(shí)候,如果累沒有進(jìn)行過(guò)初始化。則需要先觸發(fā)其初始化。
使用反射進(jìn)行反射調(diào)用的時(shí)候,如果類沒有初始化,則需要先觸發(fā)其初始化。
當(dāng)初始化一個(gè)類的時(shí)候,如果其父類沒有初始化,則需要先觸發(fā)其父類的初始化
程序啟動(dòng)需要觸發(fā)main方法的時(shí)候,虛擬機(jī)會(huì)先觸發(fā)這個(gè)類的初始化
當(dāng)使用jdk1.7的動(dòng)態(tài)語(yǔ)言支持的時(shí)候,如果一個(gè)java.lang.invoke.MethodHandler實(shí)例最后的解析結(jié)果為REF_getStatic、REF_pusStatic、REF_invokeStatic的方法句柄(句柄中包含了對(duì)象的實(shí)例數(shù)據(jù)和類型數(shù)據(jù),句柄是訪問(wèn)對(duì)象的一種方法。句柄存儲(chǔ)在堆中),并且句柄對(duì)應(yīng)的類沒有被初始化,那么需要先觸發(fā)這個(gè)類的初始化。
5種之外情況就是被動(dòng)引用。被動(dòng)引用的經(jīng)典例子有:
通過(guò)子類引用父類的靜態(tài)字段 這種情況不會(huì)導(dǎo)致子類的初始化,因?yàn)閷?duì)于靜態(tài)字段,只有直接定義靜態(tài)字段的類才會(huì)被觸發(fā)初始化,子類不是定義這個(gè)靜態(tài)字段的類,自然不能被實(shí)例化。
通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)該類的初始化 例如, Clazz[] arr = new Clazz[10];并不會(huì)觸發(fā)。
常量不會(huì)觸發(fā)定義常量的類的初始化 因?yàn)槌A吭诰幾g階段會(huì)存入調(diào)用常量的類的常量池中,本質(zhì)上并沒有引用定義這個(gè)常量的類,所以不會(huì)觸發(fā)定義這個(gè)常量的類的初始化。
對(duì)于這5種主動(dòng)引用會(huì)觸發(fā)類進(jìn)行初始化的場(chǎng)景,在java虛擬機(jī)規(guī)范中限定了“有且只有”這5種場(chǎng)景會(huì)觸發(fā)類的加載。
在加載階段虛擬機(jī)需要完成以下三件事:
通過(guò)一個(gè)類的全限定名稱來(lái)獲取此類的二進(jìn)制字節(jié)流
將這個(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)入口
這三件事在Java虛擬機(jī)中并沒有說(shuō)的很詳細(xì),比如類的全限定名稱是如何加載進(jìn)來(lái)的,以及從哪里加載進(jìn)來(lái)的。通常來(lái)講,一個(gè)類的全限定名稱可以從zip、jar包中加載,也可以從網(wǎng)絡(luò)中獲取,也可以在運(yùn)行的時(shí)候生成(這點(diǎn)最明顯的技術(shù)體現(xiàn)就是反射機(jī)制)。
對(duì)于類的加載,可以分為數(shù)組類型和非數(shù)組類型,對(duì)于非數(shù)組類型可以通過(guò)系統(tǒng)的引導(dǎo)類加載器進(jìn)行加載,也可以通過(guò)自定義的類加載器進(jìn)行加載。這點(diǎn)是比較靈活的。而對(duì)于數(shù)組類型,數(shù)組類本身不通過(guò)類加載器進(jìn)行加載,而是通過(guò)Java虛擬機(jī)直接進(jìn)行加載的,那么是不是數(shù)組類型的類就不需要類加載器了呢?答案是否定的。因?yàn)楫?dāng)數(shù)組去除所有維度之后的類型最終還是要依靠類加載器進(jìn)行加載的,所以數(shù)組類型的類與類加載器的關(guān)系還是很密切的。
通常一個(gè)數(shù)組類型的類進(jìn)行加載需要遵循以下的原則:
如果數(shù)組的組件類型(也就是數(shù)組類去除一個(gè)維度之后的類型,比如對(duì)于二維數(shù)組,去除一個(gè)維度之后是一個(gè)一維數(shù)組)是引用類型,那么遞歸采用上面的過(guò)程加載這個(gè)組件類型
如果數(shù)組類的組件類型不是引用類型,比如是基本數(shù)據(jù)類型,Java虛擬機(jī)將把數(shù)組類標(biāo)記為與引導(dǎo)類加載器關(guān)聯(lián)
數(shù)組類的可見性與組件類型的可見性是一致的。如果組件類型不是引用類型,那么數(shù)組類的可見性是public,意味著組件類型的可見性也是public。
前面已經(jīng)介紹過(guò),加載階段與連接階段是交叉進(jìn)行的,所以可能加載階段還沒有完成,連接階段就已經(jīng)開始。但是即便如此,記載階段與連接階段之間的開始順序仍然保持著固定的順序。
驗(yàn)證階段的目的是為了確保Class字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)的安全。
我們知道Java語(yǔ)言具有相對(duì)的安全性(這里的安全性體現(xiàn)為兩個(gè)方面:一是Java語(yǔ)言本身特性,比如Java去除指針,這點(diǎn)可以避免對(duì)內(nèi)存的直接操作;二是Java所提供的沙箱運(yùn)行機(jī)制,Java保證所運(yùn)行的機(jī)制都是在沙箱之內(nèi)運(yùn)行的,而沙箱之外的操作都不可以運(yùn)行)。但是需要注意的是Java虛擬機(jī)處理的Class文件并不一定是是從Java代碼編譯而來(lái),完全可能是來(lái)自其他的語(yǔ)言,甚至可以直接通過(guò)十六進(jìn)制編輯器書寫Class文件(當(dāng)然前提是編寫的Class文件符合規(guī)范)。從這個(gè)角度講,其他來(lái)源的Class文件是不可能都保證其安全性的。所以如果Java虛擬機(jī)都信任其加載進(jìn)來(lái)的Class文件,那么很有可能會(huì)造成對(duì)虛擬機(jī)自身的危害。
虛擬機(jī)的驗(yàn)證階段主要完后以下4項(xiàng)驗(yàn)證:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證。(結(jié)合前文,查看Class類文件結(jié)構(gòu))
這里的文件格式是指Class的文件規(guī)范,這一步的驗(yàn)證主要保證加載的字節(jié)流(在計(jì)算機(jī)中不可能是整個(gè)Class文件,只有0和1,也就是字節(jié)流)符合Class文件的規(guī)范(根據(jù)前面對(duì)Class類文件的描述,Class文件的每一個(gè)字節(jié)表示的含義都是確定的。比如前四個(gè)字節(jié)是否是一個(gè)魔數(shù)等)以及保證這個(gè)字節(jié)流可以被虛擬機(jī)接受處理。
在Hotspot的規(guī)范中,對(duì)文件格式的驗(yàn)證遠(yuǎn)不止這些,但是只有通過(guò)文件格式的驗(yàn)證才能進(jìn)入方法區(qū)中進(jìn)行存儲(chǔ)。所以自然也就知道,后面階段的驗(yàn)證工作都是在方法區(qū)中進(jìn)行的。
元數(shù)據(jù)可以理解為描述數(shù)據(jù)的數(shù)據(jù),更通俗的說(shuō),元數(shù)據(jù)是描述類之間的依賴關(guān)系的數(shù)據(jù),比如Java語(yǔ)言中的注解使用(使用@interface創(chuàng)建一個(gè)注解)。元數(shù)據(jù)驗(yàn)證主要目的是對(duì)類的元數(shù)據(jù)信息進(jìn)行語(yǔ)義校驗(yàn),保證不存在不符合Java語(yǔ)言規(guī)范(Java語(yǔ)法)的元數(shù)據(jù)信息。
具體的驗(yàn)證信息包括以下幾個(gè)方面:
這個(gè)類是否有父類(除了java.lang.Object外其余的類都應(yīng)該有父類)
這個(gè)類的父類是否繼承了不允許被繼承的類(比如被final修飾的類)
如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了其父類或者接口中要求實(shí)現(xiàn)的方法
類中的字段、方法是否與父類產(chǎn)生矛盾(比如是否覆蓋了父類的final字段)
這個(gè)階段主要對(duì)類的方法體進(jìn)行校驗(yàn)分析。通過(guò)了字節(jié)碼的驗(yàn)證并不代表就是沒有問(wèn)題的,但是如果沒有通過(guò)驗(yàn)證就一定是有問(wèn)題的。整個(gè)字節(jié)碼的驗(yàn)證過(guò)程比這個(gè)復(fù)雜的多,由于字節(jié)碼驗(yàn)證的高度復(fù)雜性,在jdk1.6版本之后的虛擬機(jī)增加了一項(xiàng)優(yōu)化,Class類文件結(jié)構(gòu)這篇文章中說(shuō)到過(guò)有一個(gè)屬性:StackMapTable屬性??梢院?jiǎn)單理解這個(gè)屬性是用于檢查類型是否匹配。
這個(gè)驗(yàn)證是最后階段的驗(yàn)證,符號(hào)引用是Class文件的邏輯符號(hào),直接引用指向的方法區(qū)中某一個(gè)地址,在解析階段,將符號(hào)引用轉(zhuǎn)為直接引用,這里只進(jìn)行轉(zhuǎn)化前的匹配性校驗(yàn)。符號(hào)引用驗(yàn)證主要是對(duì)類自身以外的信息進(jìn)行匹配性校驗(yàn)。比如符號(hào)引用是否通過(guò)字符串描述的全限定名是否能夠找到對(duì)應(yīng)點(diǎn)類。
符號(hào)引用(Symbolic Reference) 符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)引用可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義的定位到目標(biāo)即可(符號(hào)字面量,還沒有涉及到內(nèi)存)。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載在內(nèi)存中。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可以各不相同,但是他們能接受的符號(hào)引用必須都是一致的,因?yàn)榉?hào)引用的字面量形式明確定義在Java虛擬機(jī)規(guī)范的Class文件格式中。
直接引用(Direct Reference) 直接引用可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄(可以理解為內(nèi)存地址)。直接引用是與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一個(gè)符號(hào)引用在不同的虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般都不相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
進(jìn)行符號(hào)引用驗(yàn)證的目的在于確保解析動(dòng)作能夠正常執(zhí)行,如果無(wú)法通過(guò)符號(hào)引用驗(yàn)證那么將會(huì)拋出java.lang.IncomingChangeError異常的子類。
完成了驗(yàn)證階段之后,就進(jìn)入準(zhǔn)備階段。準(zhǔn)備階段是正式為變量分配內(nèi)存空間并且設(shè)置類變量初始值。
需要注意的是,這時(shí)候進(jìn)行內(nèi)存分配的僅僅是類變量(也就是被static修飾的變量),實(shí)例變量是不包括的,實(shí)例變量的初始化是在對(duì)象實(shí)例化的時(shí)候進(jìn)行初始化,而且分配的內(nèi)存區(qū)域是Java堆。這里的初始值也就是在編程中默認(rèn)值,也就是零值。
例如public static int value = 123 ;value在準(zhǔn)備階段后的初始值是0而不是123,因?yàn)榇藭r(shí)尚未執(zhí)行任何的Java方法,而把value賦值為123的putStatic指令是程序被編譯后,存放在類構(gòu)造器clinit()方法之中,把value賦值為123的動(dòng)作將在初始化階段才會(huì)執(zhí)行。
特殊情況:如果類字段的字段屬性表中存在ConstantValue屬性,那在準(zhǔn)備階段變量就會(huì)被初始化為ConstantValue屬性所指定的值,例如public static final int value = 123 編譯時(shí)javac將會(huì)為value生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)置將變量賦值為123。
解析階段是將常量池中的符號(hào)引用替換為直接引用的過(guò)程(前面已經(jīng)提到了符號(hào)引用與直接引用的區(qū)別)。在進(jìn)行解析之前需要對(duì)符號(hào)引用進(jìn)行解析,不同虛擬機(jī)實(shí)現(xiàn)可以根據(jù)需要判斷到底是在類被加載器加載的時(shí)候?qū)ΤA砍氐姆?hào)引用進(jìn)行解析(也就是初始化之前),還是等到一個(gè)符號(hào)引用被使用之前進(jìn)行解析(也就是在初始化之后)。
到現(xiàn)在我們已經(jīng)明白解析階段的時(shí)機(jī),那么還有一個(gè)問(wèn)題是:如果一個(gè)符號(hào)引用進(jìn)行多次解析請(qǐng)求,虛擬機(jī)中除了invokedynamic指令外,虛擬機(jī)可以對(duì)第一次解析的結(jié)果進(jìn)行緩存(在運(yùn)行時(shí)常量池中記錄引用,并把常量標(biāo)識(shí)為一解析狀態(tài)),這樣就避免了一個(gè)符號(hào)引用的多次解析。
解析動(dòng)作主要針對(duì)的是類或者接口、字段、類方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用。這里主要說(shuō)明前四種的解析過(guò)程。
要把一個(gè)類或者接口的符號(hào)引用解析為直接引用,需要以下三個(gè)步驟:
如果該符號(hào)引用不是一個(gè)數(shù)組類型,那么虛擬機(jī)將會(huì)把該符號(hào)代表的全限定名稱傳遞給調(diào)用這個(gè)符號(hào)引用的類。這個(gè)過(guò)程由于涉及驗(yàn)證過(guò)程所以可能會(huì)觸發(fā)其他相關(guān)類的加載
如果該符號(hào)引用是一個(gè)數(shù)組類型,并且該數(shù)組的元素類型是對(duì)象。我們知道符號(hào)引用是存在方法區(qū)的常量池中的,該符號(hào)引用的描述符會(huì)類似”[java/lang/Integer”的形式(描述符的概念詳見前文【深入理解JVM】:Class類文件結(jié)構(gòu)),將會(huì)按照上面的規(guī)則進(jìn)行加載,虛擬機(jī)將會(huì)生成一個(gè)代表此數(shù)組對(duì)象的直接引用
如果上面的步驟都沒有出現(xiàn)異常,那么該符號(hào)引用已經(jīng)在虛擬機(jī)中產(chǎn)生了一個(gè)直接引用,但是在解析完成之前需要對(duì)符號(hào)引用進(jìn)行驗(yàn)證,主要是確認(rèn)當(dāng)前調(diào)用這個(gè)符號(hào)引用的類是否具有訪問(wèn)權(quán)限,如果沒有訪問(wèn)權(quán)限將拋出java.lang.IllegalAccess異常
對(duì)字段的解析需要首先對(duì)其所屬的類進(jìn)行解析,因?yàn)樽侄问菍儆陬惖?,只有在正確解析得到其類的正確的直接引用才能繼續(xù)對(duì)字段的解析。對(duì)字段的解析主要包括以下幾個(gè)步驟:
如果該字段符號(hào)引用(后面簡(jiǎn)稱符號(hào))就包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用,解析結(jié)束
否則,如果在該符號(hào)的類實(shí)現(xiàn)了接口,將會(huì)按照繼承關(guān)系從下往上遞歸搜索各個(gè)接口和它的父接口,如果在接口中包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,那么久直接返回這個(gè)字段的直接引用,解析結(jié)束
否則,如果該符號(hào)所在的類不是Object類的話,將會(huì)按照繼承關(guān)系從下往上遞歸搜索其父類,如果在父類中包含了簡(jiǎn)單名稱和字段描述符都相匹配的字段,那么直接返回這個(gè)字段的直接引用,解析結(jié)束
否則,解析失敗,拋出java.lang.NoSuchFieldError異常 如果最終返回了這個(gè)字段的直接引用,就進(jìn)行權(quán)限驗(yàn)證,如果發(fā)現(xiàn)不具備對(duì)字段的訪問(wèn)權(quán)限,將拋出java.lang.IllegalAccessError異常
進(jìn)行類方法的解析仍然需要先解析此類方法的類,在正確解析之后需要進(jìn)行如下的步驟:
類方法和接口方法的符號(hào)引用是分開的,所以如果在類方法表中發(fā)現(xiàn)class_index(類中方法的符號(hào)引用)的索引是一個(gè)接口,那么會(huì)拋出java.lang.IncompatibleClassChangeError的異常
如果class_index的索引確實(shí)是一個(gè)類,那么在該類中查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)字段相匹配的方法,如果有的話就返回這個(gè)方法的直接引用,查找結(jié)束
否則,在該類的父類中遞歸查找是否具有簡(jiǎn)單名稱和描述符都與目標(biāo)字段相匹配的字段,如果有,則直接返回這個(gè)字段的直接引用,查找結(jié)束
否則,在這個(gè)類的接口以及它的父接口中遞歸查找,如果找到的話就說(shuō)明這個(gè)方法是一個(gè)抽象類,查找結(jié)束,返回java.lang.AbstractMethodError異常(因?yàn)槌橄箢愂菦]有實(shí)現(xiàn)的)
否則,查找失敗,拋出java.lang.NoSuchMethodError異常 如果最終返回了直接引用,還需要對(duì)該符號(hào)引用進(jìn)行權(quán)限驗(yàn)證,如果沒有訪問(wèn)權(quán)限,就拋出java.lang.IllegalAccessError異常
同類方法解析一樣,也需要先解析出該方法的類或者接口的符號(hào)引用,如果解析成功,就進(jìn)行下面的解析工作:
如果在接口方法表中發(fā)現(xiàn)class_index的索引是一個(gè)類而不是一個(gè)接口,那么也會(huì)拋出java.lang.IncompatibleClassChangeError的異常
否則,在該接口方法的所屬的接口中查找是否具有簡(jiǎn)單名稱和描述符都與目標(biāo)字段相匹配的方法,如果有的話就直接返回這個(gè)方法的直接引用。查找結(jié)束
否則,在該接口以及其父接口中查找,直到Object類,如果找到則直接返回這個(gè)方法的直接引用 否則,查找失敗
接口的所有方法都是public,所以不存在訪問(wèn)權(quán)限問(wèn)題
到了初始化階段,虛擬機(jī)才開始真正執(zhí)行Java程序代碼,前文講到對(duì)類變量的初始化,但那是僅僅賦初值,用戶自定義的值還沒有賦給該變量。只有到了初始化階段,才開始真正執(zhí)行這個(gè)自定義的過(guò)程,所以也可以說(shuō)初始化階段是執(zhí)行類構(gòu)造器方法clinit() 的過(guò)程。那么這個(gè)clinit() 方法是這么生成的呢?
clinit() 是編譯器自動(dòng)收集類中所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊合并生成的。編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序決定的。靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語(yǔ)句塊可以賦值,但是不能訪問(wèn)。 示例代碼:
public class Test { static{ i =0; //給變量賦值可以正常編譯通過(guò) System.out.println(i); //這句編譯器會(huì)提示“非法向前引用” } static int i = 1;}
clinit() 方法與類的構(gòu)造器方法不同,因?yàn)榍罢卟恍枰@式調(diào)用父類構(gòu)造器,因?yàn)樘摂M機(jī)會(huì)保證在子類的clinit() 方法執(zhí)行之前,父類的clinit() 方法已經(jīng)執(zhí)行完畢
由于父類的clinit() 方法會(huì)先執(zhí)行,所以就表示父類的static方法會(huì)先于子類的clinit() 方法執(zhí)行。如下面的例子所示,輸出結(jié)果為2而不是1。
public class Parent { public static int A = 1; static{ A = 2; } } public class Sub extends Parent{ public static int B = A; } public class Test { public static void main(String[] args) { System.out.println(Sub.B); } }
clinit()方法對(duì)于類或者接口來(lái)說(shuō)并不是必需的,如果一個(gè)類中沒有靜態(tài)語(yǔ)句塊也沒有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生成clinit()方法。
接口中不能使用靜態(tài)語(yǔ)句塊,但仍然有變量賦值的初始化操作,因此接口也會(huì)生成clinit()方法。但是接口與類不同,執(zhí)行接口的clinit()方法不需要先執(zhí)行父接口的clini>()方法。只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也不會(huì)執(zhí)行接口的clinit()方法。
虛擬機(jī)會(huì)保證一個(gè)類的clinit()方法在多線程環(huán)境中被正確地加鎖和同步。如果有多個(gè)線程去同時(shí)初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的clinit()方法,其它線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行clinit()方法完畢。如果在一個(gè)類的clinit()方法中有耗時(shí)很長(zhǎng)的操作,那么就可能造成多個(gè)進(jìn)程阻塞。
注意:解析和初始化在繼承關(guān)系中從下往上遞歸搜索父類的特性,可以用來(lái)解釋繼承關(guān)系中的父類和子類的初始化順序(另一個(gè)原因是Java HotSpot虛擬機(jī)的內(nèi)存布局分配策略的影響)。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司,的支持。
當(dāng)前名稱:JVM類加載機(jī)制該如何解析-創(chuàng)新互聯(lián)
文章路徑:http://jinyejixie.com/article18/ceoddp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站內(nèi)鏈、云服務(wù)器、服務(wù)器托管、網(wǎng)站改版、網(wǎng)站營(yíng)銷、營(yíng)銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容