欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java JVM類加載機(jī)制解讀

 更新時(shí)間:2021年11月17日 10:30:51   作者:小玄ks  
JVM將class文件字節(jié)碼文件加載到內(nèi)存中, 并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),在堆(并不一定在堆中,HotSpot在方法區(qū)中)中生成一個(gè)代表這個(gè)類的java.lang.Class 對(duì)象,作為方法區(qū)類數(shù)據(jù)的訪問入口,接下來將詳細(xì)講解JVM類加載機(jī)制

1.什么是類加載

首先你要知道一個(gè)類的從被加載到虛擬機(jī)內(nèi)存中開始,到被初始化為止,是為類加載的整個(gè)過程。下圖就是類加載的整個(gè)過程:

在這里插入圖片描述

一個(gè)類只有經(jīng)歷了加載、驗(yàn)證、準(zhǔn)備、解析、初始化這五個(gè)關(guān)卡才能被認(rèn)為是實(shí)現(xiàn)了類加載。這,就是類加載。

注意一點(diǎn):上面五個(gè)過程并不是按部就班地“完成”,而是按部就班地“執(zhí)行”(除解析過程外)。執(zhí)行時(shí)一定是先開始加載,再開始驗(yàn)證,但加載過程中也可能會(huì)直接開始驗(yàn)證。

2.類加載的過程

2.1加載

“加載”只是是“類加載”過程的第一個(gè)階段,關(guān)于在什么時(shí)候開始,規(guī)范并沒有進(jìn)行強(qiáng)制約束,可以讓虛擬機(jī)自行把握。在這個(gè)階段中,Java虛擬機(jī)需要完成以下三件事:

1)通過一個(gè)類的全限定名來獲取這個(gè)類的二進(jìn)制字節(jié)流

2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

3)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的方問入口

可以用一句話概括:加載是一個(gè)讀取Class文件,將其轉(zhuǎn)化為某種靜態(tài)數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)在方法區(qū)內(nèi),并在堆中生成一個(gè)便于用戶調(diào)用的java.lang.Class類型的對(duì)象的過程

2.2驗(yàn)證

驗(yàn)證是連接階段的第一步,這個(gè)階段的目的是確保Class文件的字節(jié)流中包含的信息符合約束要求,,保證這些信息被當(dāng)做代碼運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全。

這一過程了解即可。

2.3準(zhǔn)備

準(zhǔn)備階段是正式為類中定義的變量(這里說的是靜態(tài)變量,也就是被static修飾的變量)分配內(nèi)存,并設(shè)置類變量初始值的階段。

這里有兩點(diǎn)需要強(qiáng)調(diào):

1)首先這里進(jìn)行內(nèi)存分配的僅僅是類變量,而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中。

2)其次這里設(shè)置的初始值“通常情況”下是數(shù)據(jù)的零值,而不是用戶本身對(duì)它賦的初值。

如下代碼:

public static int a = 10;

變量a在準(zhǔn)備階段后的初始值是0,而不是10,因?yàn)楝F(xiàn)在只是在類加載過程中,還沒有執(zhí)行任何方法。

上面說到“通常情況”,那就說明還有特殊情況咯,加修飾詞final時(shí):

public static final int a = 10;

這時(shí)在準(zhǔn)備階段虛擬機(jī)就會(huì)將a設(shè)置為10。其實(shí)也不難理解:我們將它設(shè)置為常量,那就肯定在任何時(shí)候都不能修改啊,天子犯法與庶民同罪!

2.4解析

解析階段是Java虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程,這一過程也可能在初始化后進(jìn)行,并不一定和流程圖的執(zhí)行順序一樣。

符號(hào)引用:符號(hào)引用以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可。

直接引用:直接引用是可以直接指向目標(biāo)的指針、相對(duì)偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄。

這一過程比較復(fù)雜,有興趣可以參考《深入理解Java虛擬機(jī)》

2.5初始化【重中之重之重中重】

類的初始化階段是類加載過程的最后一個(gè)階段。在這個(gè)階段Java虛擬機(jī)才開始真正執(zhí)行類中編寫的Java程序代碼。

初始化階段有以下六種情況必須立即對(duì)類進(jìn)行“初始化”:

  • 1)使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候
  • 2)讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候
  • 3)調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候
  • 4)使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
  • 5)當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。
  • 6)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。

光說不行,主要看

第一段代碼:

package com.bit.JVMTest;

class Father {

    public  static int a = 10;
    
    static {
        System.out.println("爸爸靜態(tài)代碼塊");
    }
}


class Son extends Father{

    public static int b = 20;
    
    static {
        System.out.println("兒子靜態(tài)代碼塊");
    }
}


public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Son.b);
    }
}

運(yùn)行結(jié)果:
爸爸靜態(tài)代碼塊
兒子靜態(tài)代碼塊
20

首先Son.b是在讀取Son類自己的靜態(tài)字段,這點(diǎn)符合上面六中情況的第二種:讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候需要進(jìn)行初始化。

其次Son類繼承Father類,也就符合第五條:當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化,所以我們先初始化的應(yīng)該是Father類,然后是Son類。

因此,打印的內(nèi)容首先是爸爸靜態(tài)代碼塊(父類先初始化),然后是兒子靜態(tài)代碼塊(子類再初始化),最后是我們想要打印的b(20)本身。

再看

第二段代碼:

package com.bit.JVMTest;

class  grandFather{
    static{
        System.out.println("爺爺靜態(tài)代碼塊");
    }
}

class Father extends grandFather{

    public  static int a = 10;
    
    static {
        System.out.println("爸爸靜態(tài)代碼塊");
    }
}


class Son extends Father{

    public static int b = 20;
    
    static {
        System.out.println("兒子靜態(tài)代碼塊");
    }
}


public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Son.a);
    }
}

運(yùn)行結(jié)果:
爺爺靜態(tài)代碼塊
爸爸靜態(tài)代碼塊
10

首先要明確:Son.a是在讀取父類Father類的靜態(tài)字段(注意a字段在Son類的父類中),而不是讀取Son類本身的靜態(tài)字段

因此這次不會(huì)初始化Son類本身。

因此這次不會(huì)初始化Son類本身。

因此這次不會(huì)初始化Son類本身。

其它的和第一段代碼很相似:JVM在初始化Father類的時(shí)候,發(fā)現(xiàn)這個(gè)類還有一個(gè)父類沒有被初始化,那就先初始化它的父類:grandFather。

因此,打印的內(nèi)容首先是爺爺靜態(tài)代碼塊(Father類的父類先初始化),然后是爸爸靜態(tài)代碼塊(Father類再初始化),最后是我們想要打印的a(10)本身。

第三段代碼:

package com.bit.JVMTest;

class  grandFather{
    static{
        System.out.println("爺爺靜態(tài)代碼塊");
    }
}

class Father extends grandFather{

    public final static int a = 10;
    
    static {
        System.out.println("爸爸靜態(tài)代碼塊");
    }
}


class Son extends Father{

    public static int b = 20;
    
    static {
        System.out.println("兒子靜態(tài)代碼塊");
    }
}


public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Son.a);
    }
}

運(yùn)行結(jié)果:10

看到這里是不是想說臥**你*個(gè)*。

別急別急,這里的主函數(shù)調(diào)用雖然和第二段代碼一樣,但是注意?。?!我們給a這個(gè)靜態(tài)字段加了一個(gè)final修飾符。

再看六條中的第(2)條:讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候會(huì)觸發(fā)類加載。

也就是說我們讀取的a是被final修飾的,讀取這種靜態(tài)字段并不會(huì)引起任何類的初始化,所以就直接打印a(10)了。

再看

最后一段代碼:

package com.bit.JVMTest;


class Father {

   public Father(){
       System.out.println("爸爸構(gòu)造方法");
   }

    static {
        System.out.println("爸爸靜態(tài)代碼塊");
    }

    {
        System.out.println("爸爸普通代碼塊");
    }
}

class Son extends Father{
    public Son(){
        System.out.println("兒子構(gòu)造方法");
    }

    static {
        System.out.println("兒子靜態(tài)代碼塊");
    }

    {
        System.out.println("兒子普通代碼塊");
    }
}

public class ClassLoaderTest extends Son{
    public static void main(String[] args) {
        System.out.println("開始");
        new Son();//這里實(shí)例化一個(gè)Son類的對(duì)象
        System.out.println("結(jié)束");
    }
}

運(yùn)行結(jié)果:
 爸爸靜態(tài)代碼塊
 兒子靜態(tài)代碼塊
 開始
 爸爸普通代碼塊
 爸爸構(gòu)造方法
 兒子普通代碼塊
 兒子構(gòu)造方法
 結(jié)束

看到這里是不是欲哭無淚,我**不學(xué)了我。別急先聽我細(xì)細(xì)分析一波~
這里有一個(gè)細(xì)節(jié):主類繼承了Son類!,這貌似沒什么啊,但是還有一個(gè)細(xì)節(jié):我們的main()方法是主類中的靜態(tài)方法!看到這里是不是明白了些什么?

沒錯(cuò)!當(dāng)我們調(diào)用main()方法的時(shí)候,就引起了主類的初始化,主類繼承Son類,Son類繼承Father類,所以就先進(jìn)行Father類的初始化:打印爸爸靜態(tài)代碼塊,接著Son類初始化:打印兒子靜態(tài)代碼塊,最后該終于我主類初始化了:代碼中沒什么可以初始化的…(尷尬)。

接下來是第二階段:執(zhí)行main()方法:

1.先打?。洪_始字樣。

2.接著是構(gòu)造 Son()實(shí)例,那么就會(huì)先構(gòu)造它的父類Father()的實(shí)例:構(gòu)造實(shí)例時(shí)按照先執(zhí)行代碼塊,再執(zhí)行構(gòu)造方法的順序來。所以就先打印了:爸爸普通代碼塊、爸爸構(gòu)造方法 這幾個(gè)大字。然后再執(zhí)行構(gòu)造Son()的實(shí)例,構(gòu)造順序一樣,所以就后打印了:兒子普通代碼塊、兒子構(gòu)造方法 這幾個(gè)大字。

3.最后打?。航Y(jié)束字樣。

此時(shí)main()才方法真正結(jié)束。

總結(jié)

我們平常所說的類加載體現(xiàn)在代碼上就是初始化這一階段,我這里結(jié)束的也僅限于此,想了解詳細(xì)的類加載可以參考《深入理解Java虛擬機(jī)》這本書,也可以看其他博主的知識(shí)總結(jié)。感謝你能看到這里!

到此這篇關(guān)于Java JVM類加載機(jī)制解讀的文章就介紹到這了,更多相關(guān)Java JVM 類加載機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解feign調(diào)用session丟失解決方案

    詳解feign調(diào)用session丟失解決方案

    這篇文章主要介紹了詳解feign調(diào)用session丟失解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • Spring中配置數(shù)據(jù)源的幾種方式

    Spring中配置數(shù)據(jù)源的幾種方式

    今天小編就為大家分享一篇關(guān)于Spring中配置數(shù)據(jù)源的幾種方式,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • java的多線程用法編程總結(jié)

    java的多線程用法編程總結(jié)

    本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。
    2016-10-10
  • 結(jié)合線程池實(shí)現(xiàn)apache?kafka消費(fèi)者組的誤區(qū)及解決方法

    結(jié)合線程池實(shí)現(xiàn)apache?kafka消費(fèi)者組的誤區(qū)及解決方法

    這篇文章主要介紹了結(jié)合線程池實(shí)現(xiàn)apache?kafka消費(fèi)者組的誤區(qū)及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • Java中生成隨機(jī)數(shù)的4種方式與區(qū)別詳解

    Java中生成隨機(jī)數(shù)的4種方式與區(qū)別詳解

    生成隨機(jī)數(shù)是我們?nèi)粘i_發(fā)經(jīng)常會(huì)遇到的一個(gè)功能,這篇文章主要給大家介紹了關(guān)于Java中生成隨機(jī)數(shù)的4種方式與區(qū)別、應(yīng)用場景的相關(guān)資料,4個(gè)方式分別是Random、ThreadLocalRandom、SecureRandom以及Math,需要的朋友可以參考下
    2021-06-06
  • mybatis plus怎么忽略映射字段

    mybatis plus怎么忽略映射字段

    這篇文章主要介紹了mybatis plus怎么忽略映射字段,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • 如何基于java隨機(jī)獲取不重復(fù)數(shù)值

    如何基于java隨機(jī)獲取不重復(fù)數(shù)值

    這篇文章主要介紹了如何基于java隨機(jī)獲取不重復(fù)數(shù)值,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • java關(guān)于調(diào)用方法的匯總

    java關(guān)于調(diào)用方法的匯總

    本文小編給大家整理了在Java中關(guān)于靜態(tài)調(diào)用和動(dòng)態(tài)調(diào)用的方法匯總,值得大家學(xué)習(xí)和參考。
    2017-11-11
  • struts2標(biāo)簽總結(jié)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    struts2標(biāo)簽總結(jié)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要為大家詳細(xì)總結(jié)了struts2標(biāo)簽的使用方法,和學(xué)習(xí)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • java中把漢字轉(zhuǎn)換成簡拼的實(shí)現(xiàn)代碼

    java中把漢字轉(zhuǎn)換成簡拼的實(shí)現(xiàn)代碼

    本篇文章是對(duì)在java中把漢字轉(zhuǎn)換成簡拼的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05

最新評(píng)論