一篇文章弄懂JVM類加載機(jī)制過程以及原理
一、做一個小測試
通過注釋,標(biāo)注出下面兩個類中每個方法的執(zhí)行順序,并寫出studentId的最終值。
package com.nezha.javase; public class Person1 { private int personId; public Person1() { setId(100); } public void setId(int id) { personId = id; } }
package com.nezha.javase; public class Student1 extends Person1 { private int studentId = 1; public Student1() { } @Override public void setId(int id) { super.setId(id); studentId = id; } public void getStudentId() { System.out.println("studentId = " + studentId); } }
package com.nezha.javase; public class Test1 { public static void main(String[] args) { Student1 student = new Student1(); System.out.println("new Student() 完畢,開始調(diào)用getStudentId()方法"); student.getStudentId(); } }
有興趣的小伙伴試一下,相信我,用System.out.println
標(biāo)記一下每個函數(shù)執(zhí)行的先后順序,如果你全對了,下面的不用看了,大佬。
二、類的初始化步驟:
- 初始化父類中的靜態(tài)成員變量和靜態(tài)代碼塊 ;
- 初始化子類中的靜態(tài)成員變量和靜態(tài)代碼塊 ;
- 初始化父類的普通成員變量和代碼塊,再執(zhí)行父類的構(gòu)造方法;
- 初始化子類的普通成員變量和代碼塊,再執(zhí)行子類的構(gòu)造方法;
三、看看你寫對了沒?
package com.nezha.javase; public class Person { private int personId; /** * 第一步,走父類無參構(gòu)造函數(shù) */ public Person() { // 1、第一步,走父類無參構(gòu)造函數(shù) System.out.println("第一步,走父類無參構(gòu)造函數(shù)"); System.out.println(""); setId(100); } /** * 第三步,通過super.setId(id);走父類發(fā)方法 * @param id */ public void setId(int id) { System.out.println("第三步,通過super.setId(id);走父類發(fā)方法~~~id="+id); personId = id; System.out.println("在父類:studentId 被賦值為 " + personId); System.out.println(""); } }
package com.nezha.javase; public class Student extends Person { private int studentId = 1; /** * 在走子類無參構(gòu)造函數(shù)前,會先執(zhí)行子類的普通成員變量初始化 * 第五步,走子類無參構(gòu)造函數(shù) */ public Student() { System.out.println("第五步,在走子類無參構(gòu)造函數(shù)前,會先執(zhí)行子類的普通成員變量初始化"); System.out.println("第六步,走子類無參構(gòu)造函數(shù)"); System.out.println(""); } /** * 第二步,走子類方法 * * 走完super.setId(id);,第四步,再回此方法 * @param id */ @Override public void setId(int id) { System.out.println("第二步,走子類方法~~id="+id); // 3、第三步,走子類方法 super.setId(id); studentId = id; System.out.println("第四步,再回此方法,在子類:studentId 被賦值為 " + studentId); System.out.println(""); } /** * 第六步,走getStudentId() */ public void getStudentId() { // 4、打印出來的值是100 System.out.println("第七步,走getStudentId()"); System.out.println("studentId = " + studentId); System.out.println(""); } }
package com.nezha.javase; public class Test1 { public static void main(String[] args) { Student1 student = new Student1(); System.out.println("new Student() 完畢,開始調(diào)用getStudentId()方法"); // 打印出來的值是100 System.out.println("#推測~~打印出來的值是100"); student.getStudentId(); } }
下面通過圖解JVM的方式,分析一下。
四、類的加載過程
1、加載
- 通過一個類的全限定名獲取定義此類的二進(jìn)制字節(jié)流;
- 將這個字節(jié)流代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu);
- 在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口;
2、鏈接
(1)驗證(Verify)
- 目的在于確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,保證被加載類的正確性,不會危害虛擬機(jī)自身安全;
- 主要包括四種驗證:文件格式驗證、元數(shù)據(jù)驗證、字節(jié)碼驗證、符號引用驗證;
(2)準(zhǔn)備(Prepare)
- 為類變量分配內(nèi)存并且設(shè)置該類變量的默認(rèn)初始值;
- 這里不包含final修飾的static,因為final在編譯的時候就會分配了,準(zhǔn)備階段會顯示初始化;
- 這里不會為實例變量分配初始化,類變量會分配在方法區(qū)中,而實例變量是會隨著對象一起分配到堆中;
(3)解析
- 將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程
- 例如靜態(tài)代碼塊、靜態(tài)變量的顯示賦值
- 事實上,解析操作往往會伴隨著JVM在執(zhí)行完初始化之后在執(zhí)行
- 符號引用就是一組符號來描述所引用的目標(biāo)。符號引用的字面量形式明確定義在《Java虛擬機(jī)規(guī)范》的Class文件格式中。直接引用就是指- 向目標(biāo)的指針、相對偏移量或一個間接定位到目標(biāo)的句柄
- 解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對常量池中的CONSTANT_Filedref_info、CONSTANT_Class_info、CONSTANT_Methodref_info等。
3、初始化
- 初始化階段就是執(zhí)行類構(gòu)造器方法的過程;
- 此方法不需要定義,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并而來;
- 構(gòu)造器方法中指令按語句在源文件中出現(xiàn)的順序執(zhí)行;
- 類構(gòu)造器方法不同于類的構(gòu)造器。構(gòu)造器是虛擬機(jī)視角下的類構(gòu)造器;
- 若該類具有父類,JVM會保證子類的類構(gòu)造器執(zhí)行前,父類的類構(gòu)造器已經(jīng)執(zhí)行完畢;
- 虛擬機(jī)必須保證一個類的類構(gòu)造器方法在多線程下被同步加鎖;
五、類加載器的分類
JVM類加載器包括兩種,分別為引導(dǎo)類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)。
所有派生于抽象類ClassLoader的類加載器劃分為自定義類加載器。
1、啟動類加載器(引導(dǎo)類加載器)
- 啟動類加載器是使用C/C++語言實現(xiàn)的,嵌套在JVM內(nèi)部;
- Java的核心類庫都是使用引導(dǎo)類加載器加載的,比如String;
- 沒有父加載器;
- 是擴(kuò)展類加載器和應(yīng)用程序類加載器的父類加載器 ;
- 出于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類 ;
2、擴(kuò)展類加載器
- java語言編寫
- 派生于ClassLoader類
- 父類加載器為啟動類加載器
- 從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫,或從JDK的安裝目錄jre/lib/ext子目錄(擴(kuò)展目錄)下加載類庫。如果用戶創(chuàng)建的jar放在此目錄下,也會自動由擴(kuò)展類加載器加載
3、應(yīng)用程序類加載器(系統(tǒng)類加載器)
- java語言編寫
- 派生于ClassLoader類
- 父類加載器為擴(kuò)展類加載器
- 它負(fù)責(zé)加載環(huán)境變量classpath或系統(tǒng)屬性java.class.path指定路徑下的類庫
- 該類加載器是程序中默認(rèn)的類加載器,一般來說,Java應(yīng)用的類都是由它來完成加載的
- 通過ClassLoader.getSystemClassLoader()方法可以獲得該類加載器
六、類加載器子系統(tǒng)的作用
類加載器子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或網(wǎng)絡(luò)中加載class文件,class文件在文件開頭有特定的文件標(biāo)識。
ClassLoader只負(fù)責(zé)class文件的加載,至于它是否可以運行,則有執(zhí)行引擎決定。
加載的類信息存放于一塊稱為方法區(qū)的內(nèi)存空間。除了類的信息外,方法區(qū)中還會存放運行時常量池的信息,可能還包括字符串字面量和數(shù)字常量(這部分常量信息是class文件中常量池部分的內(nèi)存映射)。
七、總結(jié)
類的初始化步驟,這看似非常基礎(chǔ)的話題,卻實打?qū)嵉碾y住了很多人,還總結(jié)了更為深入JVM的類的加載過程、類加載器的分類、類加載器的作用。
到此這篇關(guān)于一篇文章弄懂JVM類加載機(jī)制過程以及原理的文章就介紹到這了,更多相關(guān)JVM類加載機(jī)制過程及原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決mybatis plus報錯Invalid bound statement
在使用MyBatis時遇到InvalidBoundStatement異常,常因多個MapperScan配置沖突或者包掃描路徑設(shè)置錯誤,解決方法包括保留一個MapperScan聲明、檢查jar包沖突、確保命名空間和掃描路徑正確,使用@TableId注解指定主鍵2024-11-11Java的System.getProperty()方法獲取大全
這篇文章主要介紹了Java的System.getProperty()方法獲取大全,羅列了System.getProperty()方法獲取各類信息的用法,具有一定的參考借鑒價值,需要的朋友可以參考下2014-12-12Java中靜態(tài)代碼塊、構(gòu)造代碼塊、構(gòu)造函數(shù)和普通代碼塊的區(qū)別
在Java中,靜態(tài)代碼塊、構(gòu)造代碼塊、構(gòu)造函數(shù)、普通代碼塊的執(zhí)行順序是一個筆試的考點,通過這篇文章希望大家能徹底了解它們之間的執(zhí)行順序,需要的朋友可以參考下2023-05-05