一篇文章弄懂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ù)的訪問(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)造器方法在多線程下被同步加鎖;
五、類(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ù)。如果用戶創(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)信息存放于一塊稱為方法區(qū)的內(nèi)存空間。除了類(lèi)的信息外,方法區(qū)中還會(huì)存放運(yùn)行時(shí)常量池的信息,可能還包括字符串字面量和數(shù)字常量(這部分常量信息是class文件中常量池部分的內(nèi)存映射)。
七、總結(jié)
類(lèi)的初始化步驟,這看似非?;A(chǔ)的話題,卻實(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)文章
Spring整合Quartz開(kāi)發(fā)代碼實(shí)例
這篇文章主要介紹了Spring整合Quartz開(kāi)發(fā)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
java使用servlet實(shí)現(xiàn)驗(yàn)證碼
這篇文章主要介紹了java使用servlet實(shí)現(xiàn)驗(yàn)證碼,簡(jiǎn)單實(shí)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Spring Cloud實(shí)戰(zhàn)技巧之使用隨機(jī)端口
這篇文章主要給大家介紹了關(guān)于Spring Cloud實(shí)戰(zhàn)技巧之使用隨機(jī)端口的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2017-06-06
深入解析Java編程中面向字節(jié)流的一些應(yīng)用
這篇文章主要介紹了Java編程中面向字節(jié)流的一些應(yīng)用,是Java入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10
詳解Kotlin:forEach也能break和continue
這篇文章主要介紹了詳解Kotlin:forEach也能break和continue的相關(guān)資料,需要的朋友可以參考下2017-06-06
從零開(kāi)始學(xué)Java之關(guān)系運(yùn)算符
今天帶大家復(fù)習(xí)Java關(guān)系運(yùn)算符,文中對(duì)Java運(yùn)算符相關(guān)知識(shí)作了詳細(xì)總結(jié),對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們很有幫助,需要的朋友可以參考下2021-08-08

