jvm虛擬機類加載機制詳解
1 概述
? Java虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存, 并對數(shù)據(jù)進行校驗、轉(zhuǎn)化解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這個過程稱為虛擬機的類加載機制。在Java語言中,類型的加載、連接和初始化都是在程序運行期間完成的。
2 類的加載時機
? 一個類型從被加載到虛擬機內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個生命周期將會經(jīng)歷加載、驗證、準(zhǔn)備、解析、初始化、使用和卸載七個階段,其中驗證、準(zhǔn)備和解析三個部分統(tǒng)稱為連接。發(fā)生順序如下:
? 《Java虛擬機規(guī)范》嚴(yán)格規(guī)定有且只有六種情況必須立即對類進行“初始化”(加載、驗證和準(zhǔn)備自然需要在此之前開始):
- 遇到new、getstatic、putstatic或invokestatic這四條字節(jié)碼指令時,如果類型沒有進行初始化,則需要先觸發(fā)其初始化階段。能夠生成這四條指令的典型Java代碼場景有:
- 使用new關(guān)鍵字實例化對象的時候
- 讀取或設(shè)置一個類型的靜態(tài)字段的時候
- 調(diào)用一個類型的靜態(tài)方法的時候
- 使用java.lang.reflect包的方法對類型進行反射調(diào)用的時候,如果還沒被初始化,則需要先觸發(fā)其初始化。
- 當(dāng)初始化類的時候,如果發(fā)現(xiàn)其父類還沒進行過初始化,則需要先觸發(fā)其父類的初始化。
- 當(dāng)虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類。虛擬機會先初始化這個主類。
- jdk1.7新加入的動態(tài)語言支持時。
- 當(dāng)一個接口中定義了jdk8新加入的默認(rèn)方法時。
? 這六種場景的行為稱為對一個類型進行主動引用。除此之外,所有引用類型的方式都不會觸發(fā)初始化,稱為被動引用。下面舉出被動引用的例子
示例1:通過子類引用父類的靜態(tài)字段,不會導(dǎo)致子類初始化
public class NotInitialization { public static void main(String[] args) { System.out.println(SubClass.value); // 只會輸出“SuperClass init”,而不會輸出“subclass init” SuperClass[] superClasses = new SuperClass[10]; // 不會輸出“SuperClass init” } } class SuperClass { static { System.out.println("SuperClass init"); } public static int value = 123; } class SubClass extends SuperClass { static { System.out.println("SubClass init!"); } }
示例2:常量在編譯階段會存入調(diào)用類的常量池中,本質(zhì)上沒有直接引用到定義常量的類,因此不會觸發(fā)定義常量的初始化
public class NotInitialization { public static void main(String[] args) { System.out.println(ConstClass.HELLOWORLD); // 不會輸出“ConstClass init!”,因為常量直接存儲到常量池中。 } } class ConstClass { static { System.out.println("ConstClass init!"); } public static final String HELLOWORLD = "hello world"; } class Test { static { i = 0; } static int i = 1; }
3 類的加載過程
3.1 加載
? 在加載階段,虛擬機需要完成以下三件事情:
通過一個類的全限定名來獲取定義此類的二進制字節(jié)流。將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的的各種數(shù)據(jù)的訪問入口。
3.2 驗證
? 驗證是連接階段的第一步,這一階段的目的是確保Class文件的字節(jié)流中包含的信息符合《Java虛擬機規(guī)范》的約束要求,保證這些信息不會危害虛擬機自身。
3.3 準(zhǔn)備
? 準(zhǔn)備階段是正式為類中定義的變量(即靜態(tài)變量,被static修飾的變量)分配內(nèi)存并設(shè)置類變量初始值的階段。
3.4 解析
? 解析階段是將常量池內(nèi)的符號引用替換為直接引用的過程。
3.5 初始化
? 初始化是類加載過程的最后一個階段,直到初始化階段,Java虛擬機才真正開始執(zhí)行類中編寫的Java程序代碼,將主導(dǎo)權(quán)移交給應(yīng)用程序。初始化階段就是執(zhí)行類構(gòu)造器的()方法的過程。
- ()方法與類的構(gòu)造函數(shù)不同,它不需要顯示地調(diào)用父類構(gòu)造器,Java虛擬機會保證在子類的()方法執(zhí)行前,父類的()方法已經(jīng)執(zhí)行完畢。因此在Java虛擬機中第一個被執(zhí)行的()方法肯定是Object。
- 由于父類的()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作,如下代碼:字段值將會是2而不是1。
public static void main(String[] args) { System.out.println(Sub.B); } static class Parent { public static int A = 1; static { A = 2; } } static class Sub extends Parent { public static int B = A; }
4 類加載器
? 類加載器用于實現(xiàn)類的加載動作,JVM中內(nèi)置了三個重要的ClassLoader,除了BootstrapClassLoader其他類加載器均由Java實現(xiàn)且全部繼承自java.lang.ClassLoader:
- BootstrapClassLoader(啟動類加載器):最頂層的加載類,負(fù)責(zé)加載
%JAVA_HOME%/lib
目錄下的jar包和類或者被-X:bootclasspath
c參數(shù)指定的路徑下的所有類。 - Extension Class Loader(擴展類加載器):主要負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的所有類庫。
- AppClassLoader(應(yīng)用程序類加載器):面向我們用戶的加載器,負(fù)責(zé)加載當(dāng)前應(yīng)用的classpath中的所有jar包和類。
4.1 雙親委派模型
? 如上圖:雙親委派模型的工作流程是如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到最頂層的啟動類加載器中,只有當(dāng)父加載器反饋自己無法完成這個加載請求時,子加載器才會嘗試自己去完成加載。
好處
Java中的類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。
實現(xiàn)
private final ClassLoader parent; protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,檢查請求的類是否已經(jīng)被加載過 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) {//父加載器不為空,調(diào)用父加載器loadClass()方法處理 c = parent.loadClass(name, false); } else {//父加載器為空,使用啟動類加載器 BootstrapClassLoader 加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { //拋出異常說明父類加載器無法完成加載請求 } if (c == null) { long t1 = System.nanoTime(); //自己嘗試加載 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
4.2 破壞雙親委派模型
? 雙親委派模型主要出現(xiàn)過3次較大規(guī)模的“被破壞”的情況。具體想了解的詳看《深入理解Java虛擬機》。
到此這篇關(guān)于 jvm虛擬機類加載機制詳解的文章就介紹到這了,更多相關(guān) jvm虛擬機類加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 會員管理系統(tǒng)之處理文件上傳功能
Spring Boot會員管理系統(tǒng)的中,需要涉及到Spring框架,SpringMVC框架,Hibernate框架,thymeleaf模板引擎。這篇文章主要介紹了Spring Boot會員管理系統(tǒng)之處理文件上傳功能,需要的朋友可以參考下2018-03-03利用ScriptEngineManager實現(xiàn)字符串公式靈活計算的方法
今天小編就為大家分享一篇利用ScriptEngineManager實現(xiàn)字符串公式靈活計算的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07java中元素排序Comparable和Comparator的區(qū)別
本文主要介紹了java中元素排序Comparable和Comparator的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12java如何用Processing生成馬賽克風(fēng)格的圖像
這篇文章主要介紹了如何用java如何用Processing生成馬賽克風(fēng)格的圖像,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Java中的BlockingQueue阻塞隊列原理以及實現(xiàn)詳解
這篇文章主要介紹了Java中的BlockingQueue阻塞隊列原理以及實現(xiàn)詳解,在最常見的使用到這個阻塞隊列的地方,就是我們耳熟能詳?shù)木€程池里面了,作為我們線程池的一大最大參與者,也是AQS的一個具體實現(xiàn),需要的朋友可以參考下2023-12-12mybatis類型轉(zhuǎn)換器如何實現(xiàn)數(shù)據(jù)加解密
這篇文章主要介紹了mybatis類型轉(zhuǎn)換器如何實現(xiàn)數(shù)據(jù)加解密,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09詳解SpringBoot中的tomcat優(yōu)化和修改
這篇文章主要介紹了詳解SpringBoot中的tomcat優(yōu)化和修改,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09tk-mybatis整合springBoot使用兩個數(shù)據(jù)源的方法
單純的使用mybaits進行多數(shù)據(jù)配置網(wǎng)上資料很多,但是關(guān)于tk-mybaits多數(shù)據(jù)源配置沒有相關(guān)材料,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下2021-12-12