摘要:類加載過(guò)程共分為加載驗(yàn)證準(zhǔn)備解析初始化使用和卸載七個(gè)階段這些階段通常都是互相交叉的混合式進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另外一個(gè)階段。
JVM類加載過(guò)程共分為加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載七個(gè)階段
這些階段通常都是互相交叉的混合式進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另外一個(gè)階段。
加載過(guò)程是JVM類加載的第一步,如果JVM配置中打開(kāi)-XX:+TraceClassLoading,我們可以在控制臺(tái)觀察到類似
[Loaded chapter7.SubClass from file:/E:/EclipseData-Mine/Jvm/build/classes/]
的輸出,這就是類加載過(guò)程的日志。
加載過(guò)程是作為程序猿最可控的一個(gè)階段,因?yàn)槟憧梢噪S意指定類加載器,甚至可以重寫loadClass方法,當(dāng)然,在jdk1.2及以后的版本中,loadClass方法是包含雙親委派模型的邏輯代碼的,所以不建議重寫這個(gè)方法,而是鼓勵(lì)重寫findClass方法。
類加載的二進(jìn)制字節(jié)碼文件可以來(lái)自jar包、網(wǎng)絡(luò)、數(shù)據(jù)庫(kù)以及各種語(yǔ)言的編譯器編譯而來(lái)的.class文件等各種來(lái)源。
加載過(guò)程主要完成如下三件工作:
1>通過(guò)類的全限定名(包名+類名)來(lái)獲取定義此類的二進(jìn)制字節(jié)流
2>將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)在方法區(qū)
3>為類生成java.lang.Class對(duì)象,并作為該類的唯一入口
這里涉及到一個(gè)概念就是類的唯一性,書(shū)上對(duì)該概念的解釋是:在類的加載過(guò)程中,一個(gè)類由類加載器和類本身唯一確定。也就是說(shuō),如果一個(gè)JVM虛擬機(jī)中有多個(gè)不同加載器,即使他們加載同一個(gè)類文件,那得到的java.lang.Class對(duì)象也是不同的。因此,只有在同一個(gè)加載器中,一個(gè)類才能被唯一標(biāo)識(shí),這叫做類加載器隔離。
驗(yàn)證驗(yàn)證過(guò)程相對(duì)來(lái)說(shuō)就有復(fù)雜一點(diǎn)了,不過(guò)驗(yàn)證過(guò)程對(duì)JVM的安全還是至關(guān)重要的,畢竟你不知道比人的代碼究竟能干出些什么。
驗(yàn)證過(guò)程主要包含四個(gè)驗(yàn)證過(guò)程:
1>文件格式驗(yàn)證
四個(gè)驗(yàn)證過(guò)程中,只有格式驗(yàn)證是建立在二進(jìn)制字節(jié)流的基礎(chǔ)上的。格式驗(yàn)證就是對(duì)文件是否是0xCAFEBABE開(kāi)頭、class文件版本等信息進(jìn)行驗(yàn)證,確保其符合JVM虛擬機(jī)規(guī)范。
2>元數(shù)據(jù)驗(yàn)證
元數(shù)據(jù)驗(yàn)證是對(duì)源碼語(yǔ)義分析的過(guò)程,驗(yàn)證的是子類繼承的父類是否是final類;如果這個(gè)類的父類是抽象類,是否實(shí)現(xiàn)了起父類或接口中要求實(shí)現(xiàn)的所有方法;子父類中的字段、方法是否產(chǎn)生沖突等,這個(gè)過(guò)程把類、字段和方法看做組成類的一個(gè)個(gè)元數(shù)據(jù),然后根據(jù)JVM規(guī)范,對(duì)這些元數(shù)據(jù)之間的關(guān)系進(jìn)行驗(yàn)證。所以,元數(shù)據(jù)驗(yàn)證階段并未深入到方法體內(nèi)。
3>字節(jié)碼驗(yàn)證
既然元數(shù)據(jù)驗(yàn)證并未深入到方法體內(nèi)部,那么到了字節(jié)碼驗(yàn)證過(guò)程,這一步就不可避免了。字節(jié)碼主要是對(duì)方法體內(nèi)部的代碼的前后邏輯、關(guān)系的校驗(yàn),例如:字節(jié)碼是否執(zhí)行到了方法體以外、類型轉(zhuǎn)換是否合理等。
當(dāng)然,這很復(fù)雜。
所以,即使是到了如今jdk1.8,也還是無(wú)法完全保證字節(jié)碼驗(yàn)證準(zhǔn)確無(wú)遺漏的。而且,如果在字節(jié)碼驗(yàn)證浪費(fèi)了大量的資源,似乎也有些得不償失。
4>符號(hào)引用驗(yàn)證
符號(hào)引用的驗(yàn)證其實(shí)是發(fā)生在符號(hào)引用向直接引用轉(zhuǎn)化的過(guò)程中,而這一過(guò)程發(fā)生在解析階段。
因?yàn)槎际球?yàn)證,所以一并在這講。符號(hào)引用驗(yàn)證做的工作主要是驗(yàn)證字段、類方法以及接口方法的訪問(wèn)權(quán)限、根據(jù)類的全限定名是否能定位到該類等。具體過(guò)程會(huì)在接下來(lái)的解析階段進(jìn)行分析。
好了,驗(yàn)證階段的工作基本就是以上四類,下面我們來(lái)看下一個(gè)階段。
相信經(jīng)歷過(guò)艱辛的驗(yàn)證階段的磨練,JVM和我們都倍感疲憊。所以,接下來(lái)的準(zhǔn)備階段給我們提供了一個(gè)相對(duì)輕松的休息階段。
準(zhǔn)備階段要做的工作很簡(jiǎn)單,他瞄準(zhǔn)了類變量這個(gè)元數(shù)據(jù),把他放進(jìn)了方法區(qū)并進(jìn)行了初始化,這里的初始化并不是
這一部分我畫(huà)了幾個(gè)圖,內(nèi)容有些多,放在另一篇文章里:解析
初始化初始化階段是我們可以大搞實(shí)驗(yàn)的一塊實(shí)驗(yàn)田。首先,初始化階段做什么?這個(gè)階段就是執(zhí)行
那么問(wèn)題來(lái)了,啥時(shí)候才會(huì)觸發(fā)一個(gè)類的初始化的操作呢?答案有且只有五個(gè):
1>在類沒(méi)有進(jìn)行過(guò)初始化的前提下,當(dāng)執(zhí)行new、getStatic、setStatic、invokeStatic字節(jié)碼指令時(shí),類會(huì)立即初始化。對(duì)應(yīng)的java操作就是new一個(gè)對(duì)象、讀取/寫入一個(gè)類變量(非final類型)或者執(zhí)行靜態(tài)方法。
2>在類沒(méi)有進(jìn)行過(guò)初始化的前提下,當(dāng)一個(gè)類的子類被初始化之前,該父類會(huì)立即初始化。
3>在類沒(méi)有進(jìn)行過(guò)初始化的前提下,當(dāng)包含main方法時(shí),該類會(huì)第一個(gè)初始化。
4>在類沒(méi)有進(jìn)行過(guò)初始化的前提下,當(dāng)使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用時(shí),該類會(huì)立即初始化。
5>在類沒(méi)有進(jìn)行過(guò)初始化的前提下,當(dāng)使用JDK1.5支持時(shí),如果一個(gè)java.langl.incoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
以上五種情況被稱作類的五種主動(dòng)引用,除此之外的任何情況都被相應(yīng)地叫做被動(dòng)引用。以下是集中常見(jiàn)的且容易迷惑人心智的被動(dòng)引用的示例:
/** 通過(guò)子類引用父類的類變量不會(huì)觸發(fā)子類的初始化操作 */ public class SuperClass { public static String value = "superClass value"; static { System.out.println("SuperClass init!"); } } public class SubClass extends SuperClass implements SuperInter{ static { System.out.println("SubClass init!"); } } public class InitTest { static { System.out.println("InitTest init!");//main第一個(gè)初始化 } public static void main(String[] args) { System.out.println(SubClass.value); } } /** output: InitTest init! SuperClass init! superClass value */
/** 通過(guò)定義對(duì)象數(shù)組的方式是不能觸發(fā)對(duì)象初始化的 */ public static void main(String[] args) { SubClass[] superArr = new SubClass[10]; } /** output: InitTest init! */
/** 引用類的final類型的類變量無(wú)法觸發(fā)類的初始化操作 */ public class SuperClass { public static final String CONSTANT_STRING = "constant"; static { System.out.println("SuperClass init!"); } } public class InitTest { static { System.out.println("InitTest init!");//main } public static void main(String[] args) { System.out.println(SuperClass.CONSTANT_STRING);//getStatic } } /** output: InitTest init! constant */
了解了什么時(shí)候出發(fā)初始化操作后,那么初始化操作的執(zhí)行順序是什么樣的?并發(fā)初始化情況下的運(yùn)行機(jī)制又如何?
JVM虛擬機(jī)規(guī)定了幾條標(biāo)準(zhǔn):
先父類后子類,(源碼中)先出現(xiàn)先執(zhí)行
向前引用:一個(gè)類變量在定義前可以賦值,但是不能訪問(wèn)。
非必須:如果一個(gè)類或接口沒(méi)有類變量的賦值動(dòng)作和static代碼塊,那就不生成
執(zhí)行接口的
同步性:
我們通過(guò)一個(gè)實(shí)例來(lái)驗(yàn)證線程的阻塞問(wèn)題:
public class SuperClass { static { System.out.println("SuperClass init!"); System.out.println("Thread.currentThread(): " + Thread.currentThread() + " excuting..."); try { Thread.sleep(1000 * 5); } catch (InterruptedException e) { e.printStackTrace(); } } } public class InitTest { static { System.out.println("InitTest init!");//main } public static void main(String[] args) throws ClassNotFoundException, InterruptedException { currentInitTest(); } public static void currentInitTest() throws InterruptedException { Runnable run = new Runnable() { @Override public void run() { System.out.println("Thread.currentThread(): " + Thread.currentThread() + " start"); new SuperClass(); System.out.println("Thread.currentThread(): " + Thread.currentThread() + " end"); } }; Thread[] threadArr = new Thread[10]; for (int i = 0; i < 10; i++) { threadArr[i] = new Thread(run); } for (Thread thread : threadArr) { thread.start(); } } } /** output: InitTest init! Thread.currentThread(): Thread[Thread-0,5,main] start Thread.currentThread(): Thread[Thread-1,5,main] start Thread.currentThread(): Thread[Thread-2,5,main] start Thread.currentThread(): Thread[Thread-7,5,main] start Thread.currentThread(): Thread[Thread-6,5,main] start Thread.currentThread(): Thread[Thread-3,5,main] start Thread.currentThread(): Thread[Thread-5,5,main] start Thread.currentThread(): Thread[Thread-9,5,main] start Thread.currentThread(): Thread[Thread-4,5,main] start Thread.currentThread(): Thread[Thread-8,5,main] start SuperClass init! Thread.currentThread(): Thread[Thread-0,5,main] excuting... Thread.currentThread(): Thread[Thread-9,5,main] end Thread.currentThread(): Thread[Thread-3,5,main] end Thread.currentThread(): Thread[Thread-6,5,main] end Thread.currentThread(): Thread[Thread-7,5,main] end Thread.currentThread(): Thread[Thread-0,5,main] end Thread.currentThread(): Thread[Thread-5,5,main] end Thread.currentThread(): Thread[Thread-4,5,main] end Thread.currentThread(): Thread[Thread-8,5,main] end Thread.currentThread(): Thread[Thread-1,5,main] end Thread.currentThread(): Thread[Thread-2,5,main] end */
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/70881.html
摘要:用一張思維導(dǎo)圖盡可能囊括一下的類加載過(guò)程的全流程。本文參考自來(lái)自周志明深入理解虛擬機(jī)第版,拓展內(nèi)容建議讀者可以閱讀下這本書(shū)。 用一張思維導(dǎo)圖盡可能囊括一下JVM的類加載過(guò)程的全流程。 本文參考自來(lái)自周志明《深入理解Java虛擬機(jī)(第2版)》,拓展內(nèi)容建議讀者可以閱讀下這本書(shū)。 showImg(http://ocxhn1mzz.bkt.clouddn.com/class%20loadin...
摘要:今日最佳對(duì)于程序員而言,所謂的二八定律指的是花百分之八十的時(shí)間去學(xué)習(xí)日常研發(fā)中不常見(jiàn)的那百分之二十的原理。 【今日最佳】對(duì)于程序員而言,所謂的二八定律指的是 花百分之八十的時(shí)間去學(xué)習(xí)日常研發(fā)中不常見(jiàn)的那百分之二十的原理。 據(jù)說(shuō)阿里某程序員對(duì)書(shū)法十分感興趣,退休后決定在這方面有所建樹(shù)。于是花重金購(gòu)買了上等的文房四寶。 一日,飯后突生雅興,一番磨墨擬紙,并點(diǎn)上了上好的檀香,頗有王羲之風(fēng)范,...
摘要:如果需要支持類的動(dòng)態(tài)加載或需要對(duì)編譯后的字節(jié)碼文件進(jìn)行解密操作等,就需要與類加載器打交道了。雙親委派模型,雙親委派模型,約定類加載器的加載機(jī)制。任何之類的字節(jié)碼都無(wú)法調(diào)用方法,因?yàn)樵摲椒ㄖ荒茉陬惣虞d的過(guò)程中由調(diào)用。 jvm系列 垃圾回收基礎(chǔ) JVM的編譯策略 GC的三大基礎(chǔ)算法 GC的三大高級(jí)算法 GC策略的評(píng)價(jià)指標(biāo) JVM信息查看 GC通用日志解讀 jvm的card table數(shù)據(jù)...
任何程序都需要加載到內(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...
摘要:驗(yàn)證驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。字節(jié)碼驗(yàn)證通過(guò)數(shù)據(jù)流和控制流分析,確定程序語(yǔ)義是合法的符合邏輯的。 看過(guò)這篇文章,大廠面試你「雙親委派模型」,硬氣的說(shuō)一句,你怕啥? 讀該文章姿勢(shì) 打開(kāi)手頭的 IDE,按照文章內(nèi)容及思路進(jìn)行代碼跟蹤與思考 手頭沒(méi)有 IDE,先收藏,回頭看 (萬(wàn)一哪次面試問(wèn)...
閱讀 2209·2021-09-02 15:11
閱讀 1517·2019-08-30 15:43
閱讀 2082·2019-08-29 13:48
閱讀 2801·2019-08-26 13:55
閱讀 2108·2019-08-23 15:09
閱讀 2905·2019-08-23 14:40
閱讀 3437·2019-08-23 14:23
閱讀 2645·2019-08-23 14:20