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