日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

从JVM看类的加载过程与对象实例化过程

發(fā)布時(shí)間:2025/6/17 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从JVM看类的加载过程与对象实例化过程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一. 類的加載過程

1. 類的加載過程大致是個(gè)什么過程?

我們編寫產(chǎn)生.java文件,這些.java文件經(jīng)過Java編譯器編譯成拓展名為.class的文件,.class文件中保存著Java代碼經(jīng)轉(zhuǎn)換后的虛擬機(jī)指令,我們需要將類的.class文件通過類加載器加載成為二進(jìn)制流進(jìn)入內(nèi)存,即JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)中的方法區(qū)中成為一種數(shù)據(jù)結(jié)構(gòu)。然后,在運(yùn)行時(shí)數(shù)據(jù)區(qū)中(沒有明確規(guī)定是在堆中)生成一個(gè)java.lang.Class對(duì)象,這個(gè)對(duì)象用來封裝加載到方法區(qū)的類的數(shù)據(jù)結(jié)構(gòu),便于向Java開發(fā)者提供用來訪問方法區(qū)中類的數(shù)據(jù)結(jié)構(gòu)的接口。

再來先放一張刻在Java程序員DNA里的圖便于直觀的討論

2. 類的具體加載過程

2.1 加載(Loading)

  • 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流(并沒有指明要從一個(gè)Class文件中獲取,可以從其他渠道,譬如:網(wǎng)絡(luò)、動(dòng)態(tài)生成、數(shù)據(jù)庫等)
  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
  • 在內(nèi)存中(對(duì)于HotSpot虛擬就而言就是方法區(qū))生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口

2.2 驗(yàn)證(Verification)

驗(yàn)證是連接階段的第一步,這一過程的目的是為了確保.Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。 驗(yàn)證階段大致會(huì)完成4個(gè)階段的檢驗(yàn)動(dòng)作:

  • 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合.Class文件格式的規(guī)范(例如,是否以魔術(shù)0xCAFEBABE開頭(cafe babe咖啡寶貝,就問你夠不夠騷氣)、主次版本號(hào)是否在當(dāng)前虛擬機(jī)的處理范圍之內(nèi)、常量池中的常量是否有不被支持的類型)
  • 元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語義分析,以保證其描述的信息符合Java語言規(guī)范的要求(例如:這個(gè)類是否有父類,除了java.lang.Object之外)
  • 字節(jié)碼驗(yàn)證:通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的
  • 符號(hào)引用驗(yàn)證:確保解析動(dòng)作能正確執(zhí)行

一個(gè)正兒八經(jīng)的.class文件字節(jié)碼格式要求如下,以下數(shù)據(jù)項(xiàng)的數(shù)量和順序都是嚴(yán)格限制死的
u2,u4,u8分別對(duì)應(yīng)2字節(jié),4字節(jié),8字節(jié)無符號(hào)數(shù)
以_info結(jié)尾表明該數(shù)據(jù)是表形式

類型名稱數(shù)量
u4magic1
u2minor_version1
u2major_version1
u2constant_pool_count1
cp_infoconstant_poolconstant_pool_count-1
u2access_flags1
u2this_class1
u2super_class1
u2interfaces_count1
u2interfacesinterface_count
u2fields_count1
field_infofieldsfields_count
u2methods_count1
method_infomethodsmethod_count
u2attributes_count1
attribute_infoattributesattributes_count

2.3 準(zhǔn)備(Preparation)

準(zhǔn)備階段是正式為類變量(static 成員變量)分配內(nèi)存并設(shè)置類變量初始值(零值)的階段,是連接階段的第二步,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量,而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在堆中。其次,這里所說的初始值“通常情況”下是數(shù)據(jù)類型的零值。
假設(shè)一個(gè)類變量的定義為: public static int value = 123;
那么,變量value在準(zhǔn)備階段過后的值為0而不是123
因?yàn)檫@時(shí)候尚未開始執(zhí)行任何Java方法,而把value賦值為123的 public static指令是在程序編譯后,存放于類構(gòu)造器<clinit>()方法之中的,所以把value賦值為123的動(dòng)作將在初始化階段才會(huì)執(zhí)行。

注意:如果是static final類型的常量屬性,它會(huì)被直接賦予所給定的初始值
假設(shè)一個(gè)常量定義為:public static final int constValue = 123;
那么,變量constValue準(zhǔn)備階段后的值就為123

原因是在加載生成.class字節(jié)碼文件時(shí),在attributes屬性表中有一個(gè)ConstantValue屬性類型,如果類變量是static final修飾的常量,就會(huì)生成一個(gè)ConstantValue屬性來進(jìn)行初始化。

2.4 解析(Resolution)

解析階段連接的最后一部,也是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用進(jìn)行。

2.5 初始化(Initialization)

類初始化階段是類加載過程的最后一步。在前面的類加載過程中,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼(字節(jié)碼)。
在準(zhǔn)備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值(零值);而在初始化階段,則根據(jù)開發(fā)者通過程序制定的主觀計(jì)劃去初始化類變量和其他資源,或者更直接地說:初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊static{}中的語句合并產(chǎn)生的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語句塊只能訪問到定義在靜態(tài)語句塊之前的變量。
(PS:類構(gòu)造器使用<clinit>()進(jìn)行初始化,而實(shí)例構(gòu)造器使用的是<init>()方法

注意:如果該類的直接父類還沒有被初始化,那么先初始化其直接父類,也就是先執(zhí)行父類的<clinit>方法

3.類的加載器

在虛擬機(jī)提供了三種類加載器:

  • 啟動(dòng)(Bootstrap)類加載器
  • 擴(kuò)展(Extension)類加載器
  • 系統(tǒng)(System)類加載器(也稱應(yīng)用類加載器)

3.1 啟動(dòng)類加載器(Bootstrap)

當(dāng)我們每天打開自己的電腦時(shí),第一個(gè)運(yùn)行的就是我們的引導(dǎo)程序,即Bootstrap,所以看這加載器的名字就知道,是一個(gè)非常底層的類加載器,它主要加載的是JVM自身需要的類,這個(gè)類是由C++實(shí)現(xiàn)的,是虛擬機(jī)自身的一部分。

它負(fù)責(zé)將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中,注意必由于虛擬機(jī)是按照文件名識(shí)別加載jar包的,如rt.jar,如果文件名不被虛擬機(jī)識(shí)別,即使把jar包丟到lib目錄下也是沒有作用的,出于安全考慮,Bootstrap啟動(dòng)類加載器只加載包名為java、javax、sun等開頭的類。

啟動(dòng)類加載器是無法被Java程序直接引用的。

3.2 擴(kuò)展類加載器(Extension)

該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載 JDK\jre\lib\ext目錄中,或者由 java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(如javax.開頭的類),開發(fā)者可以直接使用擴(kuò)展類加載器。

3.3 系統(tǒng)類加載器(System/Application)

該類加載器由 sun.misc.Launcher$AppClassLoader來實(shí)現(xiàn),它負(fù)責(zé)加載系統(tǒng)類路徑j(luò)ava -classpath或-D java.class.path指定路徑下的類庫,也就是我們經(jīng)常用到的classpath路徑,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。

注意:
應(yīng)用程序都是由這三種類加載器互相配合進(jìn)行加載的,如果有必要,我們還可以加入自定義的類加載器。
Java虛擬機(jī)對(duì)class文件采用的是按需加載的方式,也就是說當(dāng)需要使用該類時(shí)才會(huì)將它的class文件加載到內(nèi)存生成class對(duì)象,而且加載某個(gè)類的class文件時(shí),Java虛擬機(jī)采用的是雙親委派模式即把請(qǐng)求交由父類處理,它一種任務(wù)委派模式,下面我們進(jìn)一步了解它。

3.4 雙親委派模型

雙親委派模型的工作流程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把請(qǐng)求委托給父加載器去完成,依次向上,因此,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器在它的搜索范圍中沒有找到所需的類時(shí),即無法完成該加載,子加載器才會(huì)嘗試自己去加載該類。

注意不同層的類加載器不是繼承關(guān)系,而是通過組合實(shí)現(xiàn)類加載器調(diào)用上一級(jí)加載功能。

雙親委派機(jī)制:

1、當(dāng) AppClassLoader加載一個(gè)class時(shí),它首先不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給父類加載器ExtClassLoader去完成。

2、當(dāng) ExtClassLoader加載一個(gè)class時(shí),它首先也不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給BootStrapClassLoader去完成。

3、如果 BootStrapClassLoader加載失敗(例如在 $JAVA_HOME/jre/lib里未查找到該class),會(huì)使用 ExtClassLoader來嘗試加載;

4、若ExtClassLoader也加載失敗,則會(huì)使用 AppClassLoader來加載,如果 AppClassLoader也加載失敗,則會(huì)報(bào)出異常 ClassNotFoundException。

實(shí)現(xiàn)代碼:

protected sychronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{//檢查請(qǐng)求的類是否已經(jīng)被加載Class c = findLoadedClass(name);if(c == null){try{if(parent != null){c = parent.loadClass(name, false);}else{c = findBootstrapClassOrNull(name);}}catch(ClassNotFoundException e){//父類無法完成加載}if(c == null){//父類無法加載,調(diào)用本身的findClass方法來進(jìn)行類加載c = findClass(name);}}if(resolve){resolveClass(c);}return c; }

二. 創(chuàng)建對(duì)象的過程

當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),虛擬機(jī)就會(huì)為其在中分配內(nèi)存來存放對(duì)象自己的實(shí)例變量及其從父類繼承過來的實(shí)例變量(即使這些從超類繼承過來的實(shí)例變量有可能被隱藏但也會(huì)被分配空間)。在為這些實(shí)例變量分配內(nèi)存的同時(shí),這些實(shí)例變量也會(huì)被賦予默認(rèn)值(零值)。在內(nèi)存分配完成之后,Java虛擬機(jī)就會(huì)開始對(duì)新創(chuàng)建的對(duì)象按照程序猿的意志進(jìn)行初始化。在Java對(duì)象初始化過程中,主要涉及三種執(zhí)行對(duì)象初始化的結(jié)構(gòu),分別是 實(shí)例變量初始化、實(shí)例代碼塊初始化 以及 構(gòu)造函數(shù)初始化。(從父類到子類)
一個(gè)對(duì)象創(chuàng)建過程的偽代碼,instance = new Singleton();

memory = allocate(); //1.分配內(nèi)存空間 ctorInstance(memory); //2.初始化對(duì)象 instance = memory; //3.設(shè)置instance指向剛分配的內(nèi)存地址

由于JVM的重排序,又可能步驟2和步驟3的過程互換。

  • Java要求在實(shí)例化類之前,必須先實(shí)例化其超類,以保證所創(chuàng)建實(shí)例的完整性。事實(shí)上,這一點(diǎn)是在構(gòu)造函數(shù)中保證的:Java強(qiáng)制要求Object對(duì)象(Object是Java的頂層對(duì)象,沒有超類)之外的所有對(duì)象構(gòu)造函數(shù)的第一條語句必須是超類構(gòu)造函數(shù)的調(diào)用語句或者是類中定義的其他的構(gòu)造函數(shù),如果我們既沒有調(diào)用其他的構(gòu)造函數(shù),也沒有顯式調(diào)用超類的構(gòu)造函數(shù),那么編譯器會(huì)為我們自動(dòng)生成一個(gè)對(duì)超類構(gòu)造函數(shù)的調(diào)用

  • 我們?cè)诙x(聲明)實(shí)例變量的同時(shí),還可以直接對(duì)實(shí)例變量進(jìn)行賦值或者使用普通代碼塊對(duì)其進(jìn)行賦值。如果我們以這兩種方式為實(shí)例變量進(jìn)行初始化,那么它們將在構(gòu)造函數(shù)執(zhí)行之前完成這些初始化操作。
    實(shí)際上,如果我們對(duì)實(shí)例變量直接賦值或者使用實(shí)例代碼塊賦值,那么編譯器會(huì)將其中的代碼放到類的構(gòu)造函數(shù)中去,并且這些代碼會(huì)被放在對(duì)超類構(gòu)造函數(shù)的調(diào)用語句之后,構(gòu)造函數(shù)本身的代碼之前。所以從最終的初始化順序來看,這些初始化操作在構(gòu)造器之前完成。

  • 類構(gòu)造器<clinit>()與實(shí)例構(gòu)造器<init>()不同,它不需要程序員進(jìn)行顯式調(diào)用,虛擬機(jī)會(huì)保證在子類類構(gòu)造器<clinit>()執(zhí)行之前,父類的類構(gòu)造<clinit>()執(zhí)行完畢。由于父類的構(gòu)造器<clinit>()先執(zhí)行,也就意味著父類中定義的靜態(tài)代碼塊/靜態(tài)變量的初始化要優(yōu)先于子類的靜態(tài)代碼塊/靜態(tài)變量的初始化執(zhí)行。
    特別地,類構(gòu)造器<clinit>()對(duì)于類或者接口來說并不是必需的,如果一個(gè)類中沒有靜態(tài)代碼塊,也沒有對(duì)類變量的賦值操作,那么編譯器可以不為這個(gè)類生產(chǎn)類構(gòu)造器<clinit>()。此外,在同一個(gè)類加載器下,一個(gè)類只會(huì)被初始化一次,但是一個(gè)類可以任意地實(shí)例化對(duì)象。也就是說,在一個(gè)類的生命周期中,類構(gòu)造器<clinit>()最多會(huì)被虛擬機(jī)調(diào)用一次,而實(shí)例構(gòu)造器<init>()則會(huì)被虛擬機(jī)調(diào)用多次,只要程序員還在創(chuàng)建對(duì)象。

  • 一個(gè)實(shí)例變量在對(duì)象初始化的過程中會(huì)被賦值幾次?
    我們知道,JVM在為一個(gè)對(duì)象在堆中分配完內(nèi)存之后,會(huì)給每一個(gè)實(shí)例變量賦予默認(rèn)零值,這個(gè)時(shí)候?qū)嵗兞勘坏谝淮钨x值,這個(gè)賦值過程是沒有辦法避免的。如果我們?cè)诼暶鲗?shí)例變量x的同時(shí)對(duì)其進(jìn)行了賦值操作,那么這個(gè)時(shí)候,這個(gè)實(shí)例變量就被第二次賦值了。如果我們?cè)趯?shí)例代碼塊中,又對(duì)變量x做了初始化操作,那么這個(gè)時(shí)候,這個(gè)實(shí)例變量就被第三次賦值了。如果我們?cè)跇?gòu)造函數(shù)中,也對(duì)變量x做了初始化操作,那么這個(gè)時(shí)候,變量x就被第四次賦值。也就是說,在Java的對(duì)象初始化過程中,一個(gè)實(shí)例變量最多可以被初始化4次。當(dāng)然了,一般不會(huì)這樣做,除非特殊情況。

總的來說就是:父類的類構(gòu)造器<clinit>() ->子類的類構(gòu)造器<clinit>() -> 父類的成員變量和實(shí)例代碼塊 -> 父類的構(gòu)造函數(shù) -> 子類的成員變量和實(shí)例代碼塊 -> 子類的構(gòu)造函數(shù)

有關(guān)Java靜態(tài)域、塊,非靜態(tài)域、塊、構(gòu)造函數(shù)的初始化順序?
對(duì)于靜態(tài)變量、靜態(tài)初始化塊、變量、初始化塊、構(gòu)造器,它們的初始化順序以此是(靜態(tài)變量、靜態(tài)初始化塊)>(實(shí)例變量、初始化塊)> 構(gòu)造器。靜態(tài)代碼塊是在類加載時(shí)自動(dòng)執(zhí)行的,非靜態(tài)代碼塊是在創(chuàng)建對(duì)象時(shí)自動(dòng)執(zhí)行的代碼,不創(chuàng)建對(duì)象不執(zhí)行該類的非靜態(tài)代碼塊。

靜態(tài)代碼塊 與 靜態(tài)方法?
一般情況下,如果有些代碼必須在項(xiàng)目啟動(dòng)的時(shí)候就執(zhí)行的時(shí)候,需要使用靜態(tài)代碼塊,這種代碼是主動(dòng)執(zhí)行的;需要在項(xiàng)目啟動(dòng)的時(shí)候就初始化,在不創(chuàng)建對(duì)象的情況下,其他程序來調(diào)用的時(shí)候,需要使用靜態(tài)方法,這種代碼是被動(dòng)執(zhí)行的。
兩者的區(qū)別就是:靜態(tài)代碼塊是自動(dòng)執(zhí)行的;靜態(tài)方法是被調(diào)用的時(shí)候才執(zhí)行的。
作用:靜態(tài)代碼塊可用來初始化一些項(xiàng)目最常用的變量或?qū)ο?#xff1b;靜態(tài)方法可用作不創(chuàng)建對(duì)象也可能需要執(zhí)行的代碼;

總結(jié)

以上是生活随笔為你收集整理的从JVM看类的加载过程与对象实例化过程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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