日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java类验证和装载顺序_JVM类加载过程分析及验证

發(fā)布時(shí)間:2024/9/3 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java类验证和装载顺序_JVM类加载过程分析及验证 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

JVM類加載過程共分為加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載七個(gè)階段

這些階段通常都是互相交叉的混合式進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過程中調(diào)用或激活另外一個(gè)階段。

加載

加載過程是JVM類加載的第一步,如果JVM配置中打開-XX:+TraceClassLoading,我們可以在控制臺(tái)觀察到類似

[Loaded chapter7.SubClass from file:/E:/EclipseData-Mine/Jvm/build/classes/]

的輸出,這就是類加載過程的日志。

加載過程是作為程序猿最可控的一個(gè)階段,因?yàn)槟憧梢噪S意指定類加載器,甚至可以重寫loadClass方法,當(dāng)然,在jdk1.2及以后的版本中,loadClass方法是包含雙親委派模型的邏輯代碼的,所以不建議重寫這個(gè)方法,而是鼓勵(lì)重寫findClass方法。

類加載的二進(jìn)制字節(jié)碼文件可以來自jar包、網(wǎng)絡(luò)、數(shù)據(jù)庫(kù)以及各種語言的編譯器編譯而來的.class文件等各種來源。

加載過程主要完成如下三件工作:

1>通過類的全限定名(包名+類名)來獲取定義此類的二進(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è)概念就是類的唯一性,書上對(duì)該概念的解釋是:在類的加載過程中,一個(gè)類由類加載器和類本身唯一確定。也就是說,如果一個(gè)JVM虛擬機(jī)中有多個(gè)不同加載器,即使他們加載同一個(gè)類文件,那得到的java.lang.Class對(duì)象也是不同的。因此,只有在同一個(gè)加載器中,一個(gè)類才能被唯一標(biāo)識(shí),這叫做類加載器隔離。

驗(yàn)證

驗(yàn)證過程相對(duì)來說就有復(fù)雜一點(diǎn)了,不過驗(yàn)證過程對(duì)JVM的安全還是至關(guān)重要的,畢竟你不知道比人的代碼究竟能干出些什么。

驗(yàn)證過程主要包含四個(gè)驗(yàn)證過程:

1>文件格式驗(yàn)證

四個(gè)驗(yàn)證過程中,只有格式驗(yàn)證是建立在二進(jìn)制字節(jié)流的基礎(chǔ)上的。格式驗(yàn)證就是對(duì)文件是否是0xCAFEBABE開頭、class文件版本等信息進(jìn)行驗(yàn)證,確保其符合JVM虛擬機(jī)規(guī)范。

2>元數(shù)據(jù)驗(yàn)證

元數(shù)據(jù)驗(yàn)證是對(duì)源碼語義分析的過程,驗(yàn)證的是子類繼承的父類是否是final類;如果這個(gè)類的父類是抽象類,是否實(shí)現(xiàn)了起父類或接口中要求實(shí)現(xiàn)的所有方法;子父類中的字段、方法是否產(chǎn)生沖突等,這個(gè)過程把類、字段和方法看做組成類的一個(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)證過程,這一步就不可避免了。字節(jié)碼主要是對(duì)方法體內(nèi)部的代碼的前后邏輯、關(guān)系的校驗(yàn),例如:字節(jié)碼是否執(zhí)行到了方法體以外、類型轉(zhuǎn)換是否合理等。

當(dāng)然,這很復(fù)雜。

所以,即使是到了如今jdk1.8,也還是無法完全保證字節(jié)碼驗(yàn)證準(zhǔn)確無遺漏的。而且,如果在字節(jié)碼驗(yàn)證浪費(fèi)了大量的資源,似乎也有些得不償失。

4>符號(hào)引用驗(yàn)證

符號(hào)引用的驗(yàn)證其實(shí)是發(fā)生在符號(hào)引用向直接引用轉(zhuǎn)化的過程中,而這一過程發(fā)生在解析階段。

因?yàn)槎际球?yàn)證,所以一并在這講。符號(hào)引用驗(yàn)證做的工作主要是驗(yàn)證字段、類方法以及接口方法的訪問權(quán)限、根據(jù)類的全限定名是否能定位到該類等。具體過程會(huì)在接下來的解析階段進(jìn)行分析。

好了,驗(yàn)證階段的工作基本就是以上四類,下面我們來看下一個(gè)階段。

準(zhǔn)備

相信經(jīng)歷過艱辛的驗(yàn)證階段的磨練,JVM和我們都倍感疲憊。所以,接下來的準(zhǔn)備階段給我們提供了一個(gè)相對(duì)輕松的休息階段。

準(zhǔn)備階段要做的工作很簡(jiǎn)單,他瞄準(zhǔn)了類變量這個(gè)元數(shù)據(jù),把他放進(jìn)了方法區(qū)并進(jìn)行了初始化,這里的初始化并不是或者操作,準(zhǔn)備階段只是將這些可愛的類變量置零。

解析

這一部分我畫了幾個(gè)圖,內(nèi)容有些多,放在另一篇文章里:解析

初始化

初始化階段是我們可以大搞實(shí)驗(yàn)的一塊實(shí)驗(yàn)田。首先,初始化階段做什么?這個(gè)階段就是執(zhí)行方法。而方法是由編譯器按照源碼順序依次掃描類變量的賦值動(dòng)作和static代碼塊得到的。

那么問題來了,啥時(shí)候才會(huì)觸發(fā)一個(gè)類的初始化的操作呢?答案有且只有五個(gè):

1>在類沒有進(jìn)行過初始化的前提下,當(dāng)執(zhí)行new、getStatic、setStatic、invokeStatic字節(jié)碼指令時(shí),類會(huì)立即初始化。對(duì)應(yīng)的java操作就是new一個(gè)對(duì)象、讀取/寫入一個(gè)類變量(非final類型)或者執(zhí)行靜態(tài)方法。

2>在類沒有進(jìn)行過初始化的前提下,當(dāng)一個(gè)類的子類被初始化之前,該父類會(huì)立即初始化。

3>在類沒有進(jìn)行過初始化的前提下,當(dāng)包含main方法時(shí),該類會(huì)第一個(gè)初始化。

4>在類沒有進(jìn)行過初始化的前提下,當(dāng)使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用時(shí),該類會(huì)立即初始化。

5>在類沒有進(jìn)行過初始化的前提下,當(dāng)使用JDK1.5支持時(shí),如果一個(gè)java.langl.incoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

以上五種情況被稱作類的五種主動(dòng)引用,除此之外的任何情況都被相應(yīng)地叫做被動(dòng)引用。以下是集中常見的且容易迷惑人心智的被動(dòng)引用的示例:

/**

通過子類引用父類的類變量不會(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

*/

/**

通過定義對(duì)象數(shù)組的方式是不能觸發(fā)對(duì)象初始化的

*/

public static void main(String[] args) {

SubClass[] superArr = new SubClass[10];

}

/**

output:

InitTest init!

*/

/**

引用類的final類型的類變量無法觸發(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è)類變量在定義前可以賦值,但是不能訪問。

非必須:如果一個(gè)類或接口沒有類變量的賦值動(dòng)作和static代碼塊,那就不生成方法.

執(zhí)行接口的方法不需要先執(zhí)行父接口的方法。只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的方法。

同步性:方法的執(zhí)行具有同步性,并且只執(zhí)行一次。但當(dāng)一個(gè)線程執(zhí)行該類的方法時(shí),其他的初始化線程需阻塞等待。

我們通過一個(gè)實(shí)例來驗(yà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

*/

總結(jié)

以上是生活随笔為你收集整理的java类验证和装载顺序_JVM类加载过程分析及验证的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。