摘要:在加載階段,虛擬機(jī)要完成件事情通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。前面的階段中,除了加載的時(shí)候,可以由用戶指定自定義類加載器之外,別的都是由虛擬機(jī)主導(dǎo)控制。
java類加載機(jī)制
代碼編譯的結(jié)果從本地機(jī)器碼轉(zhuǎn)變?yōu)樽止?jié)碼,是存儲(chǔ)格式發(fā)展的一小步,確實(shí)編程語言發(fā)展的一大步
虛擬機(jī)把描述類的數(shù)據(jù)從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型,這就是虛擬機(jī)的類加載機(jī)制。
1 類的生命周期一個(gè)類從被加載到內(nèi)存到卸載出內(nèi)存,整個(gè)生命周期包括:
加載loading
驗(yàn)證verification
準(zhǔn)備preparation
解析resolution
初始化initialization
使用using
卸載unloading
其中驗(yàn)證、準(zhǔn)備和解析,這三步合起來又被稱為連接(liking)。
加載、驗(yàn)證、準(zhǔn)備、初始化和卸載,這五個(gè)階段的順序是確定的,而解析不一定。某些情況下,解析可能在初始化之后再開始,這就是java動(dòng)態(tài)綁定。
java虛擬機(jī)規(guī)范中嚴(yán)格規(guī)定了有且只有5種情況必須對(duì)類立即進(jìn)行初始化:
遇到new、getstatic、putstatic或invokestatic這四個(gè)指令時(shí),必須進(jìn)行初始化。
生成這幾個(gè)指令的場(chǎng)景有:
使用new實(shí)例化一個(gè)對(duì)象時(shí);
讀取或者設(shè)置一個(gè)類的靜態(tài)字段時(shí);
調(diào)用一個(gè)類的靜態(tài)方法時(shí)。
使用reflect包的方法對(duì)類進(jìn)行反射時(shí),也觸發(fā)初始化。
初始化一個(gè)類的時(shí)候,若父類還未初始化,則首先進(jìn)行父類的初始化。
包含main方法的那個(gè)類,虛擬機(jī)啟動(dòng)時(shí)會(huì)首先初始化這個(gè)主類。
當(dāng)使用jdk1.7的動(dòng)態(tài)語言支持時(shí),
接口的加載和類加載的過程稍有些不同:
接口和類一樣都有初始化過程,雖然接口里面不能有static{}語句塊,但是編譯器仍然會(huì)為接口生成
java接口中的變量必須得是final靜態(tài)的,但接口里最好不要有變量。
當(dāng)一個(gè)類初始化時(shí),必須要求父類全部都已經(jīng)初始化,但是接口在初始化時(shí)并不要求其父接口也全部初始化,只有在使用到父接口時(shí)才會(huì)初始化。
2 類加載的過程 2.1 加載加載是類加載的一個(gè)階段。在加載階段,虛擬機(jī)要完成3件事情:
通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。
加載階段完成之后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中。
2.2 驗(yàn)證驗(yàn)證階段是連接階段的第一步,目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
文件格式驗(yàn)證
驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。
元數(shù)據(jù)驗(yàn)證
對(duì)字節(jié)碼描述的信息進(jìn)行語義分析,以保證其描述信息符合java語言規(guī)范。
字節(jié)碼驗(yàn)證
最復(fù)雜的一個(gè)階段,通過對(duì)數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的。
符號(hào)引用驗(yàn)證
對(duì)類自身以外(常量池中的各種符號(hào)引用)的信息進(jìn)行匹配性校驗(yàn)。目的是為了確保解析動(dòng)作能正常執(zhí)行。
驗(yàn)證階段是非常重要的,但不是一定必要的階段。如果所運(yùn)行的代碼都已經(jīng)被反復(fù)使用和驗(yàn)證過,就可以通過jvm參數(shù)來關(guān)閉大部分類驗(yàn)證措施。
2.3 準(zhǔn)備準(zhǔn)備階段是給類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。
此時(shí)進(jìn)行內(nèi)存分配的變量?jī)H包括類變量,而不包含實(shí)例變量,實(shí)例變量將在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中。
這里所說的初始值是指數(shù)據(jù)類型的零值,比如:
public static int v = 123;
那v的值在準(zhǔn)備階段是0,而不是123。
數(shù)據(jù)類型 | 零值 | 數(shù)據(jù)類型 | 零值 |
---|---|---|---|
int | 0 | boolean | false |
long | 0L | float | 0.0f |
short | (short)0 | double | 0.0d |
char | "u0000" | reference | null |
byte | (byte)0 |
如果一個(gè)變量是常量,或者final類型的,那么在準(zhǔn)備階段就被初始化為常量值,如:
public static final int v = 123;
此時(shí)v的值在準(zhǔn)備階段是123。
2.4 解析解析階段是虛擬機(jī)將常量池的符號(hào)引用替換成直接引用的過程。
符號(hào)引用:是以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用的時(shí)候能無歧義的定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定加載到內(nèi)存中。各種虛擬機(jī)的內(nèi)存布局可以各不相同,但是能接受的符號(hào)引用必須是一致的,因?yàn)榉?hào)引用的字面量形式明確定義在java虛擬機(jī)規(guī)范的Class文件格式中。
直接引用:直接引用可以是直接指向目標(biāo)的指針、相對(duì)偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存相關(guān)的,如果有了直接引用,那么引用的目標(biāo)必定已經(jīng)在內(nèi)存中了。
解析主要是針對(duì)類或者接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用進(jìn)行的。
2.5 初始化類初始化時(shí)類加載過程的最后一步。前面的階段中,除了加載的時(shí)候,可以由用戶指定自定義類加載器之外,別的都是由虛擬機(jī)主導(dǎo)控制。初始化階段才真正執(zhí)行類中定義的java代碼。
在準(zhǔn)備階段變量已經(jīng)被賦過零值,而初始化階段是根據(jù)程序里面的來初始化類變量和其他資源,可以理解為執(zhí)行類構(gòu)造器的
public class Test{ static{ i=0; //這句話是給變量賦值,可以編譯通過 System.out.println(i); //這句話是要訪問i,編譯器會(huì)提示“非法向前引用”編譯不過。 } static int i = 1; }
由上一條可以得出結(jié)論,父類中定義的靜態(tài)語句塊要早于子類的變量賦值操作。
前面加載的時(shí)候有說到,接口中不能有靜態(tài)語句塊,但是可以有變量的初始化賦值操作。接口和類都會(huì)生成
虛擬機(jī)會(huì)保證一個(gè)類的
類加載階段的加載階段,即“通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到j(luò)vm外部實(shí)現(xiàn),使得應(yīng)用程序自己可以決定如何獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。
對(duì)于任意一個(gè)類來說,需要加載它的類加載器和其類本身來保證唯一性。如果同一個(gè)Class文件,被不同的類加載器加載了,那么產(chǎn)生的兩個(gè)類是不相同的。
3.1 類加載器的分類對(duì)于java虛擬機(jī)來說,只有兩種不同的類加載器:
啟動(dòng)類加載器 Bootstrap ClassLoader:C++實(shí)現(xiàn)的,虛擬機(jī)的一部分。
其他類加載器:java語言實(shí)現(xiàn),獨(dú)立于jvm外部。全部繼承抽象類java.lang.ClassLoader。
從java程序員的角度來看,有三種系統(tǒng)提供的類加載器:
啟動(dòng)類加載器 Bootstrap ClassLoader
負(fù)責(zé)將放在JAVA_HOEM/lib目錄里的,或者是被-Xbootclasspath參數(shù)指定的路徑中的,并且可以被虛擬機(jī)識(shí)別的類庫加載到虛擬機(jī)內(nèi)存中。
啟動(dòng)類加載器無法被java程序直接引用。如果是用戶在編寫自定義類加載器的時(shí)候,需要把加載請(qǐng)求委派給啟動(dòng)類加載器,返回null就行了。
擴(kuò)展類加載器 Extension ClassLoader
負(fù)責(zé)加載JAVA_HOEM/lib/ext目錄中的,或者被java.ext.dirs系統(tǒng)變量指定的所有類庫,開發(fā)者可以直接使用擴(kuò)展類加載器。
應(yīng)用程序類加載器 Application ClassLoader
這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法中的返回值,所以也稱為系統(tǒng)類加載器,負(fù)責(zé)加載用戶類路徑上指定的類庫。
開發(fā)者可以直接使用此類加載器,如果應(yīng)用程序沒有自定義自己的類加載器,一般情況下這個(gè)就是程序的默認(rèn)類加載器。
開發(fā)者可以自己編寫一些自定義類加載器,用來進(jìn)行特定類的加載。他們的關(guān)系是:
雙親委派模型
3.1 雙親委派模型雙親委派模型要求除了最頂層的啟動(dòng)類加載器外,其余的加載器都得有自己的父類加載器。這里的類加載器的父子關(guān)系不是通過繼承來實(shí)現(xiàn),而是使用組合關(guān)系來復(fù)用復(fù)加載器的代碼。
雙親委派模型的工作過程是:
如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是這樣。因此,所有的類加載請(qǐng)求最終都會(huì)傳送到最頂層的啟動(dòng)類加載器,只有當(dāng)父加載器反饋?zhàn)约簾o法加載這個(gè)加載請(qǐng)求的時(shí)候,子加載器才會(huì)嘗試自己去加載。
使用這個(gè)模型的好處就是java類隨著它的加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。比如java.lang.Object,無論哪個(gè)類加載器要加載這個(gè)類的時(shí)候,最終都是委派給最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各個(gè)類加載器環(huán)境中都是同一個(gè)類。如果不使用這個(gè)模型的話,由各個(gè)類加載器自己加載,就會(huì)出現(xiàn)多個(gè)Object類。
雙親委派模型的邏輯實(shí)現(xiàn)代碼很簡(jiǎn)單:
protected synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException{ //首先檢查請(qǐng)求的類是否已經(jīng)被加載過 Class c = findLoadedClass(name); if(c==null){ if(parent!=null){ c=parent.loadClass(name, false); }else{ c=findBootstrapClassOrNull(name); } //如果父類加載器無法加載的時(shí)候,就調(diào)用本身的方法去加載 if(c==null){ c=findClass(name); } } if(resolve){ resolveClass(c); } return c; }3.2 破壞雙親委派模型
雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,在java世界中,大部分加載器都遵循這個(gè)模型,在java歷史上有三種比較大的被破壞情況。
第一次是jdk1.2發(fā)布的時(shí)候。由于雙親委派模型是在1.2才引入的,java.lang.ClassLoader是在1.0的時(shí)候就存在了,面對(duì)在此之前的用戶自定義類加載器的代碼,java設(shè)計(jì)者添加了一個(gè)findClass方法來作為妥協(xié)。
第二次是JNDI服務(wù)。雙親委派模型很好地解決了各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問題,但是當(dāng)基礎(chǔ)類又回來調(diào)用用戶的代碼就沒辦法了。所以引入了線程上下文 類加載器(Thread Context ClassLaoder)。
第三次就是熱更新熱部署的時(shí)候。代表就是OSGi,每一個(gè)程序模塊都有一個(gè)自己的類加載器,當(dāng)需要更換一個(gè)模塊的時(shí)候,就把模塊連同其加載器一起換掉。此時(shí)的類加載器的結(jié)構(gòu)成了網(wǎng)狀結(jié)構(gòu)了。
4 寫在最后把書從后面往前面看還是挺有意思的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70758.html
摘要:如果需要支持類的動(dòng)態(tài)加載或需要對(duì)編譯后的字節(jié)碼文件進(jìn)行解密操作等,就需要與類加載器打交道了。雙親委派模型,雙親委派模型,約定類加載器的加載機(jī)制。任何之類的字節(jié)碼都無法調(diào)用方法,因?yàn)樵摲椒ㄖ荒茉陬惣虞d的過程中由調(diào)用。 jvm系列 垃圾回收基礎(chǔ) JVM的編譯策略 GC的三大基礎(chǔ)算法 GC的三大高級(jí)算法 GC策略的評(píng)價(jià)指標(biāo) JVM信息查看 GC通用日志解讀 jvm的card table數(shù)據(jù)...
摘要:前面提到,對(duì)于數(shù)組類來說,它并沒有對(duì)應(yīng)的字節(jié)流,而是由虛擬機(jī)直接生成的。對(duì)于其他的類來說,虛擬機(jī)則需要借助類加載器來完成查找字節(jié)流的過程。驗(yàn)證階段的目的,在于確保被加載類能夠滿足虛擬機(jī)的約束條件。 Java 虛擬機(jī)將字節(jié)流轉(zhuǎn)化為 Java 類的過程。這個(gè)過程可分為加載、鏈接以及初始化 三大步驟。 加載是指查找字節(jié)流,并且據(jù)此創(chuàng)建類的過程。加載需要借助類加載器,在 Java 虛擬機(jī)中,類...
摘要:當(dāng)前類加載器和所有父類加載器都無法加載該類時(shí),拋出異常。加載兩份相同的對(duì)象的情況和不屬于父子類加載器關(guān)系,并且各自都加載了同一個(gè)類。類加載機(jī)制與接口當(dāng)虛擬機(jī)初始化一個(gè)類時(shí),不會(huì)初始化該類實(shí)現(xiàn)的接口。 類加載機(jī)制 概念 類加載器把class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,存放在方法區(qū),然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對(duì)象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。 1、加載: 查...
摘要:當(dāng)程序使用某個(gè)類時(shí),如果該類還沒被初始化,加載到內(nèi)存中,則系統(tǒng)會(huì)通過加載連接初始化三個(gè)過程來對(duì)該類進(jìn)行初始化。一旦一個(gè)類被加載到中之后,就不會(huì)再次載入了。它既可以從本地文件系統(tǒng)獲取二進(jìn)制文件來加載類,也可以遠(yuǎn)程主機(jī)獲取二進(jìn)制文件來加載類。 當(dāng)程序使用某個(gè)類時(shí),如果該類還沒被初始化,加載到內(nèi)存中,則系統(tǒng)會(huì)通過加載、連接、初始化三個(gè)過程來對(duì)該類進(jìn)行初始化。該過程就被稱為類的初始化 類加載 ...
摘要:學(xué)習(xí)能更深入的理解這門語言,能理解語言底層的執(zhí)行過程,深入到字節(jié)碼層次。 目錄 ? 前言 程序的運(yùn)行 1.JVM類加載機(jī)制 ①一般在什么情況下會(huì)去加載一個(gè)類?也就是說,什么時(shí)候.class字節(jié)碼文件中加載這個(gè)類到JVM內(nèi)存里來? ②驗(yàn)證、準(zhǔn)備、初始化 ③初始化 2.類加載器和雙親委派機(jī)制 ...
摘要:以上文中的類的加載過程為例,它的加載器為系統(tǒng)類加載器。自定義加載器編寫自定義加載器并不困難,只要繼承抽象類并覆蓋方法就行了。源碼來自參考資料類加載機(jī)制與類加載器架構(gòu)深入探討類加載器 序 我是在關(guān)于Java的面試題里了解到類加載器的,在這之前從未想過Java里類是如何被加載、解析的,一直以為只要Import就好了。事實(shí)上Java類加載器是一塊非常重要的內(nèi)容,可以用在類層次劃分、OSGi、...
閱讀 2812·2021-10-14 09:42
閱讀 3619·2021-10-11 10:59
閱讀 2952·2019-08-30 11:25
閱讀 3088·2019-08-29 16:25
閱讀 3233·2019-08-26 17:40
閱讀 1241·2019-08-26 13:30
閱讀 1155·2019-08-26 11:46
閱讀 1337·2019-08-23 15:22