自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

死磕JVM五年 該知道JVM加載機(jī)制了!

開發(fā) 后端
Java虛擬機(jī)類加載過程是把Class類文件加載到內(nèi)存,并對(duì)Class文件中的數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型的過程

[[401398]]

類加載

  • Java虛擬機(jī)類加載過程是把Class類文件加載到內(nèi)存,并對(duì)Class文件中的數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型的過程

和那些編譯時(shí)需要連接工作的語言不同,在Java語言里,類型的加載,連接和初始化過程都是在程序 運(yùn)行期間完成的,這種策略雖然會(huì)令類加載時(shí)稍微增加一些性能開銷,但是會(huì)為java應(yīng)用程序提供比較高的靈活性。

當(dāng)我們使用到某個(gè)類的時(shí)候,如果這個(gè)類還未從磁盤上加載到內(nèi)存中,JVM就會(huì)通過三步走策略(加載、連接、初始化)來對(duì)這個(gè)類進(jìn)行初始化,JVM完成這三個(gè)步驟的名稱,就叫做類加載或者類初始化

類加載的時(shí)機(jī)

什么情況下需要開始類加載的第一個(gè)階段——加載 ,在Java虛擬機(jī)規(guī)范中沒有進(jìn)行強(qiáng)制約束,而是交給虛擬機(jī)的具體實(shí)現(xiàn)來進(jìn)行把握,但是對(duì)于初始化階段,虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了 “有且只有” 五種情況必須立即對(duì)類進(jìn)行初始化(而加載、驗(yàn)證、準(zhǔn)備自然需要在此之前開始),具體情況如下所示:

class文件的加載時(shí)機(jī):

關(guān)于序號(hào)1的詳細(xì)解釋:

  • 使用 new 關(guān)鍵字實(shí)例化對(duì)象時(shí)
  • 讀取類的靜態(tài)變量時(shí)(被 final修飾,已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)
  • 設(shè)置類的靜態(tài)變量時(shí)
  • 調(diào)用一個(gè)類的靜態(tài)方法時(shí)

注意: newarray指令觸發(fā)的只是數(shù)組類型本身的初始化,而不會(huì)導(dǎo)致其相關(guān)類型的初始化,比如, newString[]只會(huì)直接觸發(fā) String[]類的初始化,也就是觸發(fā)對(duì)類 [Ljava.lang.String的初始化,而直接不會(huì)觸發(fā) String類的初始化。

生成這四條指令最常見的Java代碼場(chǎng)景是:

對(duì)于這5種會(huì)觸發(fā)類進(jìn)行初始化的場(chǎng)景,虛擬機(jī)規(guī)范中使用了一個(gè)很強(qiáng)烈的限定語:“有且只有”,這5種場(chǎng)景中的行為稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,稱為 被動(dòng)引用。

需要特別指出的是,類的實(shí)例化和類的初始化是兩個(gè)完全不同的概念:

類的實(shí)例化是指創(chuàng)建一個(gè)類的實(shí)例(對(duì)象)的過程;

類的初始化是指為類各個(gè)成員賦初始值的過程,是類生命周期中的一個(gè)階段;

被動(dòng)引用的三個(gè)場(chǎng)景:

通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化

  1. /** 
  2.  * @program: jvm 
  3.  * @ClassName Test1 
  4.  * @Description:通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化 
  5.  * @author: 牧小農(nóng) 
  6.  * @create: 2021-02-27 11:42 
  7.  * @Version 1.0 
  8.  **/ 
  9. public class Test1 { 
  10.  
  11.     static { 
  12.         System.out.println("Init Superclass!!!"); 
  13.     } 
  14.  
  15.     public static void main(String[] args) { 
  16.                  int x = Son.count
  17.     } 
  18.  
  19.  
  20. class Father extends Test1{ 
  21.     static int count = 1; 
  22.     static { 
  23.         System.out.println("Init father!!!"); 
  24.     } 
  25.  
  26. class Son extends Father{ 
  27.     static { 
  28.         System.out.println("Init son!!!"); 
  29.     } 

輸出:

  1. Init Superclass!!! 
  2. Init father!!! 

對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此通過其子類來引用父類中定義的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化而不會(huì)觸發(fā)子類的初始化。至于是否要觸發(fā)子類的加載和驗(yàn)證,在虛擬機(jī)中并未明確規(guī)定,這點(diǎn)取決于虛擬機(jī)的具體實(shí)現(xiàn)。對(duì)于Sun HotSpot虛擬機(jī)來說,可通過-XX:+TraceClassLoading參數(shù)觀察到此操作會(huì)導(dǎo)致子類的加載。

上面的案例中,由于count字段是在Father類中定義的,因此該類會(huì)被初始化,此外,在初始化類Father的時(shí)候,虛擬機(jī)發(fā)現(xiàn)其父類Test1 還沒被初始化,因此虛擬機(jī)將先初始化其父類Test1 ,然后初始化子類Father,而Son始終不會(huì)被初始化;

通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化

  1. /** 
  2.  * @program: jvm 
  3.  * @ClassName Test2 
  4.  * @description: 
  5.  * @author: muxiaonong 
  6.  * @create: 2021-02-27 12:03 
  7.  * @Version 1.0 
  8.  **/ 
  9. public class Test2 { 
  10.  
  11.     public static void main(String[] args) { 
  12.         M[] m = new M[8]; 
  13.     } 
  14.  
  15.  
  16. class M{ 
  17.     static { 
  18.         System.out.println("Init M!!!"); 
  19.     } 

運(yùn)行之后我們會(huì)發(fā)現(xiàn)沒有輸出 "Init M!!!",說明沒有觸發(fā)類的初始化階段

常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化

  1. /** 
  2.  * @program: jvm 
  3.  * @ClassName Test3 
  4.  * @description: 
  5.  * @author: muxiaonong 
  6.  * @create: 2021-02-27 12:05 
  7.  * @Version 1.0 
  8.  **/ 
  9. public class Test3 { 
  10.  
  11.     public static void main(String[] args) { 
  12.         System.out.println(ConstClass.COUNT); 
  13.     } 
  14.  
  15.  
  16. class ConstClass{ 
  17.     static final int COUNT = 1; 
  18.     static
  19.         System.out.println("Init ConstClass!!!"); 
  20.     } 

上面代碼運(yùn)行后也沒有輸出 InitConstClass!!!,這是因?yàn)殡m然在Java源碼中引用了ConstClass 類中的常量COUNT ,但其實(shí)在編譯階段通過常量傳播優(yōu)化,已經(jīng)將常量的值 "1"存儲(chǔ)到Test3 常量池中了,對(duì)常量ConstClass.COUNT的引用實(shí)際都被轉(zhuǎn)化為Test3 類對(duì)自身常量池的引用了,也就是說,實(shí)際上Test3 的Class文件之中并沒有ConstClass類的符號(hào)引用入口,這兩個(gè)類在編譯為Class文件之后就不存在關(guān)系

類加載過程

有一個(gè)名叫Class文件,它靜靜的躺在了硬盤上,吃香的喝辣的,他究竟需要一個(gè)怎么樣的過程經(jīng)歷了什么,才能夠從舒服的硬盤中到內(nèi)存中呢?class進(jìn)入內(nèi)存總共有三大步。

  • 加載(Loading)
  • 連接(Linking)
  • 初始化(Initlalizing)

1、加載

加載是 類加載(Class Loading) 過程的一個(gè)階段,加載 是 類加載(Class Loading) 過程的一個(gè)階段,加載是指將當(dāng)前類的class文件讀入內(nèi)存中,并且創(chuàng)建一個(gè) java.lang.Class的對(duì)象,也就是說,當(dāng)程序中使用任何類的時(shí)候,系統(tǒng)都會(huì)創(chuàng)建一個(gè)叫 java.lang.Class對(duì)象

在加載階段,虛擬機(jī)需要完成以下三個(gè)事情:

通過一個(gè)類的全限定名類獲取定義此類的二進(jìn)制字節(jié)流(沒有指明只能從一個(gè)Class文件中獲取,可以從其他渠道,如:網(wǎng)絡(luò)、動(dòng)態(tài)生成、數(shù)據(jù)庫(kù)等)

將這個(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ù)的訪問入口

類加載器通常無須等到“首次使用”該類時(shí)才加載該類,Java虛擬機(jī)規(guī)范允許系統(tǒng)預(yù)先加載某些類。加載階段與連接階段的部分內(nèi)容是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開始,但這些夾在夾在階段之中進(jìn)行的動(dòng)作,仍然屬于連接階段的內(nèi)容,這兩個(gè)階段的開始時(shí)間仍然保持著固定的先后順序。

2、連接

當(dāng)類被加載之后,系統(tǒng)會(huì)生成一個(gè)對(duì)應(yīng)的Class對(duì)象,就會(huì)進(jìn)入 連接階段,連接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到JRE中,連接階段又分為三個(gè)小階段

1.1 驗(yàn)證

驗(yàn)證是連接階段的第一步,這一階段的主要目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。Java語言相對(duì)于 C/C++ 來說本身是相對(duì)安全的語言,驗(yàn)證階段是非常重要的,這個(gè)階段是否嚴(yán)謹(jǐn),決定了Java虛擬機(jī)能不能承受惡意代碼的攻擊,當(dāng)驗(yàn)證輸入的字節(jié)流不符合Class文件格式的約束時(shí),虛擬機(jī)會(huì)拋出一個(gè) java.lang.VerifyError異?;蛘咦宇惍惓?,從大體來說驗(yàn)證主要分為四個(gè)校驗(yàn)動(dòng)作:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證

文件格式驗(yàn)證: 主要驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。主要包含以下幾個(gè)方面:

  • 文件格式是否以 CAFEBABE開頭
  • 主次版本是否在虛擬機(jī)處理的范圍內(nèi)
  • 常量池的常量是否有不被支持的常量類型
  • 指向常量的各種索引值是否有指向不存在的常量或者不符合類型的常量
  • CONSTANTUtf8info 型的常量是否有不符合UTF8編碼的數(shù)據(jù)
  • Class文件中各個(gè)部分及文件本身是否有被刪除的活附件的信息

元數(shù)據(jù)驗(yàn)證: 主要是對(duì)字節(jié)碼描述的信息進(jìn)行語義分析,主要目的是對(duì)類的元數(shù)據(jù)進(jìn)行語義校驗(yàn),分析是否符合Java的 語言語法的規(guī)范,保證不存在不符合Java語言的規(guī)范的元數(shù)據(jù)的信息,該階段主要驗(yàn)證的方面包含以下幾個(gè)方面:

  • 這個(gè)類是否有父類(除java.lang.Object)
  • 這個(gè)類的父類是否繼承了不允許被繼承的類(被final 修飾的類)
  • 如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了父類或接口之中要求的所有方法
  • 類中的字段、方法是否和父類產(chǎn)生矛盾

字節(jié)碼驗(yàn)證: 最重要也是最復(fù)雜的校驗(yàn)環(huán)節(jié),通過數(shù)據(jù)流和控制流分析程序語義是否合法、符合邏輯的。主要針對(duì)類的方法體進(jìn)行校驗(yàn)分析,保證被校驗(yàn)的類在運(yùn)行時(shí)不會(huì)危害虛擬機(jī)安全的事情

  • 保證任何時(shí)候操作數(shù)棧的數(shù)據(jù)類型和指令代碼序列都能配合工作(例如在操作棧上有一個(gè)int類型的數(shù)據(jù),保證不會(huì)在使用的時(shí)候按照long類型來加載到本地變量表中)
  • 跳轉(zhuǎn)指令不會(huì)條狀到方法體以外的字節(jié)碼指令上
  • 保證方法體中的數(shù)據(jù)轉(zhuǎn)換是有效的,例如可以把一個(gè)子類對(duì)象賦值給父類數(shù)據(jù)類型,但是不能把父類賦值給子類數(shù)據(jù)類型

符號(hào)引用驗(yàn)證: 針對(duì)符號(hào)引用轉(zhuǎn)換直接引用的時(shí)候,這個(gè)裝換工作會(huì)在第三階段(字節(jié)碼驗(yàn)證)解析階段中發(fā)生。主要是保證引用一定會(huì)被訪問到,不會(huì)出現(xiàn)類無法訪問的問題。

1.2 準(zhǔn)備

為類變量 分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都會(huì)在方法區(qū)進(jìn)行分配,在準(zhǔn)備階段是把class文件靜態(tài)變量賦默認(rèn)值,注意:不是賦初始值,比如我們 publicstaticinti=8,在這個(gè)步驟 并不是把 i 賦值成8 ,而是先賦值為0

基本類型的默認(rèn)值:

在通常情況下初始值是0,但是如果我們把上面的常量加一個(gè)final 類修飾的話,那么這個(gè)時(shí)候初始值就會(huì)編程我們指定的值 publicstaticfinalinti=8編譯的時(shí)候Javac會(huì)把i的初始值變?yōu)?,

1.3 解析

把class文件常量池里面用到的符號(hào)引用轉(zhuǎn)換為直接內(nèi)存地址,直接可以訪問到的內(nèi)容符號(hào)引用:以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何字面形式的字面量,只要不會(huì)出現(xiàn)沖突能夠定位到就可以直接引用:可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在了

3、初始化

初始化是給類的靜態(tài)變量賦正確的初始值,剛才我們有講到準(zhǔn)備階段是復(fù)制默認(rèn)值,而初始化是給靜態(tài)變量賦值初始值,看下面的語句:

public static int i = 8

首先字節(jié)碼文件被加載到內(nèi)存后,先進(jìn)行連接驗(yàn)證,通過準(zhǔn)備階段,給i分配內(nèi)存,因?yàn)槭莝tatic,所以這個(gè)時(shí)候i 等于int類型的默認(rèn)初始值是0,所以i 現(xiàn)在是 0,到了初始化的時(shí)候,才會(huì)真正把i 賦值為8

類加載器

類加載器負(fù)責(zé)加載所有的類,并且為載入內(nèi)存中的類生成一個(gè) java.lang.Class實(shí)例對(duì)象,如果一個(gè)類被加載到JVM中后,同一個(gè)類不會(huì)再次被載入,就像對(duì)象有一個(gè)唯一的標(biāo)識(shí),同樣載入的JVM的類也有一個(gè)唯一的標(biāo)識(shí)。JVM本身有一個(gè)類加載器的層次,這個(gè)類加載器本身就是一個(gè)普通的Class,所有的Class都是被類加載器加載到內(nèi)存中,我們可以稱之為ClassLoader,一個(gè)頂級(jí)的父類,也是一個(gè)abstract抽象類。

Bootstrap: 類加載器的加載過程,分成不同的層次來進(jìn)行加載,不同的類加載器加載不同的Class,作為最頂層的Bootstrap,它加載lib里JDK最核心的內(nèi)容,比如說rt.jar charset.jar等核心類,當(dāng)我們調(diào)用getClassLoader()拿到這個(gè)加載器結(jié)果是一個(gè)Null的時(shí)候,代表我們已經(jīng)達(dá)到了最頂層的加載器

Extension: Extension加載器擴(kuò)展類,加載擴(kuò)展包里的各種各樣的文件,這些擴(kuò)展包在JDK安裝目錄 jre/lib/ext下的jar

App: 就是我們平時(shí)用到的application ,用來加載classpath指定的內(nèi)容

Custom ClassLoader: 自定義ClassLoader,加載自己自定義的加載器 Custom ClassLoader 的父類加載器是 application 的父類加載器是 Extension的父類加載器是Bootstrap

注意:他們不是繼承關(guān)系,而是委托關(guān)系

  1. public class ClassLoaderTest { 
  2.     public static void main(String[] args) { 
  3.         // 查看是誰Load到內(nèi)存的,執(zhí)行結(jié)果是null,因?yàn)锽ootstrap使用C++實(shí)現(xiàn)的 
  4.         // 在Java里面沒有class和它對(duì)應(yīng) 
  5.         System.out.println(String.class.getClassLoader()); 
  6.  
  7.         //這個(gè)是核心類庫(kù)某個(gè)包里的類執(zhí)行,執(zhí)行結(jié)果是Null,因?yàn)樵擃愐彩潜籅ootstrap加載的 
  8.         System.out.println(sun.awt.HKSCS.class.getClassLoader()); 
  9.  
  10.         //這個(gè)類是位于ext目錄下某個(gè)jar文件里面,當(dāng)我們調(diào)用他執(zhí)行結(jié)果就是sun.misc.Launcher$ExtClassLoader@a09ee92 
  11.         System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader()); 
  12.  
  13.         // 這個(gè)是我們自己寫的ClassLoad加載器,由sun.misc.Launcher$AppClassLoader@18b4aac2加載 
  14.         System.out.println(ClassLoaderTest.class.getClassLoader()); 
  15.  
  16.         // 是Exe的ClassLoader 調(diào)用它的getclass(),它本身也是一個(gè)class,調(diào)用它的getClassLoader,他的ClassLoader的ClassLoader就是我們的Bootstrap所以結(jié)果為Null 
  17.         System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader()); 
  18.          
  19.     } 

類加載器繼承關(guān)系

這個(gè)圖講的是ClassLoader從語法上是從誰繼承的,這個(gè)圖只是單純的一個(gè)語法關(guān)系,不是繼承關(guān)系,大家可以記住,和上面的類加載沒有一點(diǎn)關(guān)系,過分的大家其實(shí)可以忽略這個(gè)圖

雙親委派

父加載器: 父加載器不是"類加載器的加載器",也不是"類加載器的父類加載器" 雙親委派是一個(gè)孩子向父親的方向,然后父親向孩子方向的雙親委派過程

當(dāng)一個(gè)類加載器收到了類加載請(qǐng)求時(shí)候,他會(huì)先嘗試從自定義里面去找,同時(shí)它內(nèi)部還維護(hù)了緩存,如果在緩存中找到了就直接返回結(jié)果,如果沒有找到,就向父類進(jìn)行委托,父類再去緩存中找,一直到最頂級(jí)的父類,如果這個(gè)時(shí)候還沒有從緩存中獲取到我們想要的結(jié)果,這個(gè)時(shí)候父親就說我你這個(gè)事情,我辦不了,你要自己動(dòng),然后兒子就自己去查詢對(duì)應(yīng)的class類并加載,如果到了最小的一個(gè)兒子還是沒有找到對(duì)應(yīng)的類,就會(huì)拋出異常 Class Not Found Exception


為什么要弄雙親委派?

這個(gè)是類加載器必問的一個(gè)面試題。

主要為了安全,如果任何一個(gè)Class都可以把他load到內(nèi)存中的話,那么我寫一個(gè) java.lang.String,如果我寫入了有危險(xiǎn)的代碼,是不是就會(huì)發(fā)生安全問題,并且可以保證Java核心api中定義的類型不會(huì)被隨意替換,可以防止API內(nèi)庫(kù)被隨意更改,其次是效率問題,如果有緩存在,直接從緩存里面拿,就不用一遍一遍的去遍歷查詢我們的父類或者子類了。

 

責(zé)任編輯:姜華 來源: 牧小農(nóng)
相關(guān)推薦

2021-02-28 11:58:33

JVM機(jī)制語言

2021-06-03 08:32:18

JVM調(diào)優(yōu)虛擬機(jī)

2021-02-25 07:21:00

JVMJavaava虛擬機(jī)

2021-06-01 09:29:43

ArthasJVM內(nèi)存

2021-06-09 07:56:51

JvmJVM面試題Java

2023-10-31 16:00:51

類加載機(jī)制Java

2021-06-02 09:55:20

JVM排查JVM內(nèi)存過高技術(shù)

2023-08-02 08:38:27

JVM加載機(jī)制

2021-03-16 05:44:26

JVM面試題運(yùn)行時(shí)數(shù)據(jù)

2024-12-02 09:01:23

Java虛擬機(jī)內(nèi)存

2021-06-16 00:57:16

JVM加載機(jī)制

2021-04-29 11:18:14

JVM加載機(jī)制

2017-03-08 10:30:43

JVMJava加載機(jī)制

2017-09-20 08:07:32

java加載機(jī)制

2010-09-26 16:55:31

JVM學(xué)習(xí)筆記

2022-10-08 08:34:34

JVM加載機(jī)制代碼

2010-09-26 16:42:04

JVM內(nèi)存組成JVM垃圾回收

2021-09-24 08:10:40

Java 語言 Java 基礎(chǔ)

2012-03-01 10:51:37

JavaJVM

2020-05-20 22:13:26

JVM加載機(jī)制虛擬機(jī)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)