摘要:一類加載的過(guò)程虛擬機(jī)加載類主要有五個(gè)過(guò)程加載驗(yàn)證準(zhǔn)備解析和初始化。初始化在虛擬機(jī)中嚴(yán)格規(guī)定需要對(duì)類進(jìn)行初始化的,有下面五種情況遇到,,或者這條字節(jié)碼指令時(shí)。
在<深入理解Java虛擬機(jī)-周志明>這本書里面,在講到類初始化的五種情況時(shí),提及了一個(gè)比較有趣的事情。先來(lái)看看下面的代碼
public class SubClass { static{ System.err.println("I m your son"); } public static final int name = 111; }
這個(gè)時(shí)候如果調(diào)用SubClass.name,是根本不會(huì)觸發(fā)SubClass初始化的(這里是因?yàn)閚ame是一個(gè)常量,和下面的例子不一樣,如果這里把final去掉,是會(huì)觸發(fā)Subclass的初始化的,因?yàn)閷?duì)于靜態(tài)字段而言,如果靜態(tài)字段被引用,就會(huì)調(diào)用getstatic指令和putstatic指令,那么自然就會(huì)引發(fā)類的初始化,詳情看下面關(guān)于觸發(fā)類初始化的五種情況)。再來(lái)看看另一種情況;
public class SuperClass { static{ System.err.println("I am your father"); } public static int value = 123; } public class SubClass extends SuperClass{ static{ System.err.println("I m your son"); } }
這個(gè)時(shí)候如果調(diào)用SubClass.value(靜態(tài)字段和靜態(tài)方法是可以繼承但是無(wú)法被覆蓋,所以這里調(diào)用value,只會(huì)導(dǎo)致直接定義這個(gè)靜態(tài)變量的類被初始化),同樣也是不會(huì)使得SubClass這個(gè)類進(jìn)行初始化。那么問(wèn)題來(lái)了,到底類在什么時(shí)候會(huì)進(jìn)行初始化,類的初始化順序到底是怎樣的?讓我們接著往下看。
一. 類加載的過(guò)程
虛擬機(jī)加載類主要有五個(gè)過(guò)程:加載、驗(yàn)證、準(zhǔn)備、解析和初始化。
加載:加載是“類加載”的一個(gè)過(guò)程,希望讀者沒(méi)有混淆這兩個(gè)概念。
在這個(gè)過(guò)程虛擬機(jī)主要完成三件事,
? 通過(guò)一個(gè)類的全限定名___[解釋全限定名]___來(lái)獲取此類的二進(jìn)制字節(jié)流,這點(diǎn)上,虛擬機(jī)并沒(méi)有指明要從哪里獲取類的二進(jìn)制字節(jié)流,因此發(fā)展出了很多不一樣的加載方式。比如jar,zip等壓縮包中加載,從網(wǎng)絡(luò)獲取[如Applet],或者由其他文件生成[如從JSP生成]。
? 將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
? 在Java堆[這個(gè)沒(méi)有強(qiáng)制規(guī)定,比如HotSpot則選擇在方法區(qū)中生成這個(gè)對(duì)象]中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為程序訪問(wèn)方法區(qū)中的各種數(shù)據(jù)的外部入口[也就是說(shuō)當(dāng)常量池表中的數(shù)據(jù)被轉(zhuǎn)換成運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)的時(shí)候,實(shí)際上[堆/方法區(qū)]有一個(gè)Class對(duì)象的實(shí)例可以訪問(wèn)到方法區(qū)的各類數(shù)據(jù),包括常量池表,代碼等]。
如果加載對(duì)象是普通的類或者接口(統(tǒng)稱為C),則是通過(guò)類加載器(L)去加載C的二進(jìn)制表示來(lái)創(chuàng)建。但是如果加載的是數(shù)組類,那情況就有所不同了,數(shù)組類本身不通過(guò)類加載器創(chuàng)建,它是由Java虛擬機(jī)直接創(chuàng)建的。但是數(shù)組類內(nèi)部的元素類型最終還是要靠類加載器去加載。[后續(xù)可以添加類加載器的詳細(xì)解釋]
驗(yàn)證
驗(yàn)證是鏈接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)本身的安全。驗(yàn)證大致上有以下4個(gè)過(guò)程:
1) 文件格式驗(yàn)證:
a) 檢查魔數(shù),主、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍。
b) 常量池的常量是否不被支持[通過(guò)檢查tag],指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量。
c) CONSTANT_Utf8_info類型的常量中是否有不符合UTF8編碼的數(shù)據(jù)。
d) Class文件中各個(gè)部分及文件本身是否有被刪除或者附加其他信息等等。
這個(gè)節(jié)點(diǎn)的主要目的是保證輸入的字節(jié)流能被正確的解析并存儲(chǔ)于方法區(qū)內(nèi),格式上符合描述一個(gè)java類型信息的要求。這個(gè)階段是基于二進(jìn)制流,只要通過(guò)了這個(gè)階段的驗(yàn)證,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)中存儲(chǔ)。所以后續(xù)的三個(gè)階段基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的,不會(huì)再直接操作字節(jié)流。
2) 元數(shù)據(jù)驗(yàn)證:這個(gè)階段是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合Java語(yǔ)言規(guī)范的要求。主要驗(yàn)證包括以下幾點(diǎn):
a) 這個(gè)類是否有父類(除了java.lang.Object外,所有的類都應(yīng)該有父類)。
b) 這個(gè)類的父類是否繼承了不允許被繼承的類(被final修飾的類)。
c) 如果這個(gè)類不是抽象類,那么應(yīng)該實(shí)現(xiàn)其父類或接口中要求實(shí)現(xiàn)的方法。
d) 類中的字段,方法是否與父類相矛盾(例如覆蓋了父類的final字段,或者出現(xiàn)不符合規(guī)則的方法重載)。
這個(gè)階段主要目的是對(duì)類的元數(shù)據(jù)信息進(jìn)行語(yǔ)義校驗(yàn),保證不存在不符合Java規(guī)范的元數(shù)據(jù)信息。
3) 字節(jié)碼驗(yàn)證:
4) 符號(hào)引用驗(yàn)證:
準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量的初始值階段,這些變量所使用的內(nèi)存都將在方法區(qū)中分配。這里有幾個(gè)值得注意的點(diǎn):
1) 這里初始化的僅僅是類變量(被static修飾的變量)的初始化,并不包括實(shí)例變量。實(shí)例變量將會(huì)在對(duì)象實(shí)例化的時(shí)候隨著對(duì)象一起分配在java堆中。
2) 這里所說(shuō)的初始值,通常是數(shù)據(jù)類型的零值,舉個(gè)例子:
public static int value = 123;
這句代碼中,value在準(zhǔn)備階段的初始值為0,而不是123,因?yàn)檫@個(gè)時(shí)候還沒(méi)開始執(zhí)行任何的java方法。而把value的值置為123的putstatic指令是程序被編譯后,存放在類構(gòu)造器
當(dāng)然,上述情況也有例外的地方,如果類字段的字段屬性表(參考class文件中的屬性數(shù)據(jù)結(jié)構(gòu))中存在ConstatntValue[即同時(shí)被final和static修飾]屬性,那么在準(zhǔn)備階段,變量value就會(huì)被初始化為ConstantValue屬性所指定的值,例如上述變量中,編譯時(shí)javac將會(huì)為value生成的ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue屬性而將value賦值為123。
解析
解析階段就是虛擬機(jī)將常量池內(nèi)的符號(hào)引用[使用一組描述符來(lái)描述所引用的目標(biāo),符可以是任意形式的字面量,只要使用時(shí)能無(wú)歧義的定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中]替換為直接引用[直接引用可以是直接指向目標(biāo)的指針,相對(duì)偏移量或者一個(gè)能間接定位到目標(biāo)的句柄。直接引用與內(nèi)存的布局有關(guān),如果有了直接引用,則目標(biāo)一定存在]的過(guò)程,符號(hào)引用在Class文件內(nèi)的常量池中以CONSTANT_Fieldref_info,CONSTANT_Class_info,CONSTANT_Methodref_info等類型出現(xiàn)。那么,解析階段中的直接引用于符號(hào)引用又有什么關(guān)聯(lián)呢?
對(duì)同一個(gè)符號(hào)引用進(jìn)行多次解析請(qǐng)求是很常見(jiàn)的,比如你在代碼里面多次new同一個(gè)類。這里要分成兩種情況:
1) invokeddynamic指令:這個(gè)指令的特殊之處在于,它是為了支持動(dòng)態(tài)語(yǔ)言而存在的,也就是說(shuō),必須等到程序?qū)嶋H運(yùn)行這條指令的時(shí)候,解析動(dòng)作才能進(jìn)行[目前僅使用java語(yǔ)言并不會(huì)生成這條指令]。相對(duì)的,其余觸發(fā)的解析指定都是“靜態(tài)”的,可以在剛剛完成加載階段,還沒(méi)開始執(zhí)行代碼時(shí)就進(jìn)行解析。
2) 除了上述的指令外,虛擬機(jī)實(shí)現(xiàn)可以對(duì)第一次解析的結(jié)果進(jìn)行緩存(在運(yùn)行時(shí)常量池中記錄直接引用,并把常量標(biāo)識(shí)為已解析狀態(tài))。從而避免了多次解析。
解析動(dòng)作主要針對(duì)“類或接口”,“字段”,“類方法”,“接口方法”,“方法類型”,“方法句柄”和“調(diào)用點(diǎn)限定符”7類符號(hào)引用進(jìn)行[分別對(duì)應(yīng)7種常量池表的CONSTATN_Class_info,CONSTATN_Fieldref_info,CONSTATN_Methodref_info,CONSTATN_InterfaceMethodref_info,CONSTATN_MethodType_info,CONSTATN_MethodHandle_info,CONSTATN_InvokeDynamic_info,后續(xù)三種和動(dòng)態(tài)類型有關(guān),目前java還是靜態(tài)類型語(yǔ)言]。
初始化
在虛擬機(jī)中嚴(yán)格規(guī)定需要對(duì)類進(jìn)行初始化的,有下面五種情況:
1) 遇到new,getstatic,putstatic或者invokestatic這4條字節(jié)碼指令時(shí)。
2) 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候。
3) 當(dāng)初始化一個(gè)類,發(fā)現(xiàn)其父類并沒(méi)有初始化時(shí),需要先初始化父類。
4) 虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會(huì)先初始化這個(gè)類。
5) 當(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)有初始化,則需要先觸發(fā)其初始化。
對(duì)于以上五種初始化場(chǎng)景,虛擬機(jī)規(guī)范中使用了“只有”,除此之外,所有的引用類的方式都不會(huì)觸發(fā)初始化。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/66901.html
摘要:新生代又被劃分為三個(gè)區(qū)域和兩個(gè)幸存區(qū)。這樣劃分的目的是為了使能夠更好地管理堆內(nèi)存中的對(duì)象,包括內(nèi)存的分配及回收。新生代主要存儲(chǔ)新創(chuàng)建的對(duì)象和尚未進(jìn)入老年代的對(duì)象。 在Java中主要有以下三種類加載器: 引導(dǎo)類加載器(bootstrap class loader) --用來(lái)加載java的核心庫(kù)(Strin...
任何程序都需要加載到內(nèi)存才能與CPU進(jìn)行交流 同理, 字節(jié)碼.class文件同樣需要加載到內(nèi)存中,才可以實(shí)例化類 ClassLoader的使命就是提前加載.class 類文件到內(nèi)存中 在加載類時(shí),使用的是Parents Delegation Model(溯源委派加載模型) Java的類加載器是一個(gè)運(yùn)行時(shí)核心基礎(chǔ)設(shè)施模塊,主要是在啟動(dòng)之初進(jìn)行類的加載、鏈接、初始化 showImg(https://s...
摘要:前面提到,對(duì)于數(shù)組類來(lái)說(shuō),它并沒(méi)有對(duì)應(yīng)的字節(jié)流,而是由虛擬機(jī)直接生成的。對(duì)于其他的類來(lái)說(shuō),虛擬機(jī)則需要借助類加載器來(lái)完成查找字節(jié)流的過(guò)程。驗(yàn)證階段的目的,在于確保被加載類能夠滿足虛擬機(jī)的約束條件。 Java 虛擬機(jī)將字節(jié)流轉(zhuǎn)化為 Java 類的過(guò)程。這個(gè)過(guò)程可分為加載、鏈接以及初始化 三大步驟。 加載是指查找字節(jié)流,并且據(jù)此創(chuàng)建類的過(guò)程。加載需要借助類加載器,在 Java 虛擬機(jī)中,類...
摘要:虛擬機(jī)有個(gè)一加載機(jī)制,叫做雙親委派模型。擴(kuò)展類加載器擴(kuò)展類加載器的父類的加載器是啟動(dòng)類加載器。驗(yàn)證驗(yàn)證的目的就是需要符合虛擬機(jī)的規(guī)范。虛擬機(jī)會(huì)通過(guò)加鎖的方式確保方法只執(zhí)行一次。 引言 上一篇文章談到Java運(yùn)行的流程,其中有一環(huán)是類加載。今天就繼續(xù)深入探討JVM如何加載虛擬機(jī)。首先JVM加載類的一般流程分三步:·加載·鏈接·初始化那么是否全部Java類都是這樣三步走的方式加載呢?我們可...
摘要:驗(yàn)證驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。字節(jié)碼驗(yàn)證通過(guò)數(shù)據(jù)流和控制流分析,確定程序語(yǔ)義是合法的符合邏輯的。 看過(guò)這篇文章,大廠面試你「雙親委派模型」,硬氣的說(shuō)一句,你怕啥? 讀該文章姿勢(shì) 打開手頭的 IDE,按照文章內(nèi)容及思路進(jìn)行代碼跟蹤與思考 手頭沒(méi)有 IDE,先收藏,回頭看 (萬(wàn)一哪次面試問(wèn)...
摘要:驗(yàn)證過(guò)程驗(yàn)證過(guò)程的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。二虛擬機(jī)字節(jié)碼執(zhí)行引擎虛擬機(jī)的執(zhí)行引擎自行實(shí)現(xiàn),可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系。 本篇博客主要針對(duì)Java虛擬機(jī)的類加載機(jī)制,虛擬機(jī)字節(jié)碼執(zhí)行引擎,早期編譯優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請(qǐng)點(diǎn)擊Java虛擬總結(jié)上篇 。 一.虛擬機(jī)類加載機(jī)制 概述 虛擬機(jī)把描述類的數(shù)據(jù)從Clas...
閱讀 1286·2021-11-25 09:43
閱讀 1653·2021-10-25 09:47
閱讀 2524·2019-08-30 13:46
閱讀 811·2019-08-29 13:45
閱讀 1335·2019-08-26 13:29
閱讀 3059·2019-08-23 15:30
閱讀 1169·2019-08-23 14:17
閱讀 1373·2019-08-23 13:43