通俗講解JVM的類加載機(jī)制
前言
我們很多小伙伴平時都是做JAVA開發(fā)的,那么作為一名合格的工程師,你是否有仔細(xì)的思考過JVM的運(yùn)行原理呢。
如果懂得了JVM的運(yùn)行原理和內(nèi)存模型,像是一些JVM調(diào)優(yōu)、垃圾回收機(jī)制等等的問題我們才能有一個更清晰的概念。
為了走進(jìn)JVM,深入了解底層,王子打算寫一個JVM的專題,留下自己對JVM探索的足跡,同時也希望能幫到小伙伴們更好的理解JVM。
那我們開始吧。
JAVA代碼的運(yùn)行流程
首先我們就來聊一聊JAVA代碼是怎么運(yùn)行起來的,這部分比較基礎(chǔ)相信大家都知道,就當(dāng)成是個復(fù)習(xí)吧。
我們編寫的代碼都是在java文件中編寫的,然后會編譯成class字節(jié)碼文件。
當(dāng)我們使用到哪個類的時候就會通過類加載器把class字節(jié)碼文件中的類加載到j(luò)vm內(nèi)存中,然后就是在jvm內(nèi)存中運(yùn)行我們的代碼了。
整體的運(yùn)行流程就是這樣,相信小伙伴們都很清楚這些,但是有關(guān)類加載器是如何把類加載到j(luò)vm內(nèi)存中的,小伙伴們有考慮過嗎?
今天我們主要就是聊這一部分。
JVM什么時候加載類
其實(shí)說到類加載的底層機(jī)制,這是一個很復(fù)雜的過程,但是對于我們平時的工作來講,只要懂得它的核心原理就可以了。
一個類的加載過程會經(jīng)歷如下的幾個過程:
加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用、卸載
首先我們就先弄明白一個問題,jvm是什么時候去加載類的呢?
其實(shí)答案很簡單,就是我們什么時候使用到了這個類,它就去class字節(jié)碼文件中去加載這個類。
而作為程序的入口,具有main方法的類,肯定是最開始的時候就加載到j(luò)vm中了。
對于加載類的時間點(diǎn)問題,其實(shí)就是這么簡單。
類加載器和雙親委派機(jī)制
既然我們知道了類加載的時間點(diǎn),那么jvm是通過什么方式對類進(jìn)行加載的呢?就是類加載器。
那接下來我們就來聊聊jvm的類加載器。
jvm的類加載器總體上可以分成4層,我們一起看一下。
1.啟動類加載器
首先就是jvm啟動的第一道關(guān)口,啟動類加載器Bootstrap ClassLoader,它主要是加載java的核心類。
相信大家都知道,無論是什么環(huán)節(jié)下運(yùn)行java程序,都是要安裝jvm虛擬機(jī)環(huán)境的,而在這個環(huán)境的目錄中是有一個lib文件夾的,這個文件下就是java最核心的類庫,支撐著java系統(tǒng)的運(yùn)行。
所以一旦jvm啟動,那么首先就會通過啟動類加載器去加載lib文件夾下的核心類庫。
2.擴(kuò)展類加載器
然后我們就到了第二層,擴(kuò)展類加載器Extension ClassLoader,這個類加載器其實(shí)與啟動類加載器是類似的。
在我們的jvm虛擬機(jī)環(huán)境目錄下,是有一個lib/ext的文件夾的,這里面的類就是java運(yùn)行環(huán)境的一些擴(kuò)展類,這些擴(kuò)展類就是在jvm啟動后,通過擴(kuò)展類加載器進(jìn)行加載的。
3.應(yīng)用程序類加載器
加載完核心類庫和擴(kuò)展類,這時候就到了第三層,應(yīng)用程序類加載器Application ClassLoader,這個類加載器你就可以理解成是加載我們寫好的java代碼的就可以了。
4.自定義類加載器
前面的三層就是基本的類加載器了,然后第四層是自定義類加載器,根據(jù)一些特殊的需求來自己定義類加載器加載我們的類。
整體上類加載器就是這么的4層結(jié)構(gòu)。很多小伙伴可能都聽說過雙親委派機(jī)制,那么什么是雙親委派機(jī)制呢,王子就和大家用最接地氣的語言描述一下。
其實(shí)很好理解,就是當(dāng)我們的類加載器要加載一個類的時候,它首先會委派給它的父親去加載,但是如果它的父親沒找到就會把這個事交給他的孩子自己去完成了。
這就是雙親委派機(jī)制。
舉個例子,假如我們的應(yīng)用程序類加載器要加載一個類A,那么首先它會先回家找它老爸?jǐn)U展類加載器,問問“老爸,你那有這個類A嗎?”
然后擴(kuò)展類加載器接到這個請求之后,同樣也懶得處理,再去找它爺爺啟動類加載器。
它爺爺找了一圈沒找到類A,很生氣,就對擴(kuò)展類加載器說,“我這沒有,你自己找去!”
然后擴(kuò)展類加載器就灰溜溜的自己找了一圈,同樣也沒找到,這時候就找到應(yīng)用類加載器了,說:“我這哪有你這個類A,這明明是你自己應(yīng)該干的活,愛上哪找上哪找去,我不管了”。
這時候應(yīng)用類加載器就只能自己去處理了,找了一圈發(fā)現(xiàn)找到了類A,就把它加載到j(luò)vm內(nèi)存中了。
相信大家看了這個例子應(yīng)該很容易理解了吧。
所以假設(shè)我們自己創(chuàng)建了一個類java.lang.String,它是不會被應(yīng)用類加載器加載到內(nèi)存中的,因?yàn)楦割愔锌梢哉业竭@個類,就直接給加載到內(nèi)存中了。
聊聊驗(yàn)證、準(zhǔn)備、解析、初始化階段
聊完了加載,我們再來看看驗(yàn)證、準(zhǔn)備、解析、初始化這幾個階段jvm都做了什么。
1.驗(yàn)證階段
這一步其實(shí)很容易理解,就是jvm根據(jù)java規(guī)范,來校驗(yàn)?zāi)慵虞d進(jìn)來的class文件中的內(nèi)容是否符合規(guī)范,如果不符合規(guī)范jvm是無法正常運(yùn)行的。
所以在加載后,首先就是驗(yàn)證階段。
2.準(zhǔn)備階段
假設(shè)我們有一個類A,剛剛加載并通過了驗(yàn)證,那么就會進(jìn)行準(zhǔn)備工作。
這個準(zhǔn)備工作其實(shí)就是給類A分配一定的內(nèi)存空間,然后給里面的靜態(tài)變量(static修飾的變量)也分配內(nèi)存空間,并賦初始值。
3.解析階段
這個階段干的事實(shí)際上是把符號引用替換為直接引用,這一過程網(wǎng)上有很多資料,還是比較復(fù)雜的,如果感興趣小伙伴們可以自己查閱一下資料。
實(shí)際工作中也很少會接觸這部分的內(nèi)容,所以我們知道有這么個階段就可以了。
4.初始化階段
在準(zhǔn)備階段,我們把類A的內(nèi)存已經(jīng)分配完了,那么初始化階段要做些什么事呢?我們先看一下類A的代碼
public class A {
private static String i=System.getProperty("i");
}
準(zhǔn)備階段我們只是給變量i分配了內(nèi)存空間,并賦值了初始值,但是后邊的System.getProperty("i")是不會執(zhí)行的。
沒錯,這部分代碼就是在初始化階段執(zhí)行的,另外靜態(tài)代碼塊也會在這一階段執(zhí)行。
舉個例子,比如我們新建一個對象new A(),此時就會觸發(fā)從加載到初始化的全過程,把這個類準(zhǔn)備好并創(chuàng)建一個實(shí)例對象。
此外這里有一個規(guī)則,如果類A繼承了類B,那么在初始化類A的時候,如果發(fā)現(xiàn)類B還沒有初始化,會先初始化類B。
擴(kuò)展
到這里關(guān)于JVM的類加載機(jī)制其實(shí)就已經(jīng)說完了,王子再給大家擴(kuò)展一個小知識點(diǎn)。
小伙伴們想過沒有,Tomcat也是用java開發(fā)的,那么它的類加載機(jī)制是什么樣的呢,為什么就能支持jsp呢?
其實(shí)它就是利用了自定義類加載器這一機(jī)制,自己自定義了很多類加載器,整體的結(jié)構(gòu)如下:

Tomcat自定義了這么多的類加載器,用來加載它自己的核心類庫,并且Tomcat是打破了雙親委派機(jī)制的,感興趣的小伙伴可以自己去查資料了解一下,王子就不在本篇文章長篇大論來聊Tomcat了。
總結(jié)
今天我們聊的內(nèi)容還是jvm中比較基礎(chǔ)的部分,以后的文章我們再慢慢深入,去探索jvm的底層原理,如果對JVM感興趣的小伙伴可以關(guān)注王子的后續(xù)文章哦,我們可以一步一個腳印的逐步分解JVM,去了解JVM的垃圾回收機(jī)制、性能調(diào)優(yōu)等等實(shí)用性問題,讓你面對JVM的面試或者生產(chǎn)實(shí)踐也可以游刃有余。
那我們下次見。
以上就是通俗講解JVM的類加載機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于JVM 類加載機(jī)制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java遞歸遍歷樹形結(jié)構(gòu)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java遞歸遍歷樹形結(jié)構(gòu)的相關(guān)資料,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2016-03-03
Springboot 整合通用mapper和pagehelper展示分頁數(shù)據(jù)的問題(附github源碼)
這篇文章主要介紹了Springboot 整合通用mapper和pagehelper展示分頁數(shù)據(jù)(附github源碼),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09
java多線程加鎖以及Condition類的使用實(shí)例解析
這篇文章主要介紹了java多線程加鎖以及Condition類的使用實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11
org.apache.ibatis.annotations不存在的問題
這篇文章主要介紹了org.apache.ibatis.annotations不存在的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
springboot的異步任務(wù):無返回值和有返回值問題
這篇文章主要介紹了springboot的異步任務(wù):無返回值和有返回值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07

