基于Java類的加載方式
類的生命周期
當(dāng)java源代碼文件被javac編譯成class文件后,并不能直接運(yùn)行,而是需要經(jīng)過加載,連接和初始化這幾個(gè)階段后才能使用。
在使用完類或被銷毀后,JVM會(huì)將類卸載掉。
類加載的過程
類加載的過程需要經(jīng)過三個(gè)階段分別是:
- 1.加載
- 2.連接
- 3.初始化,其中連接又可分為3個(gè)階段:驗(yàn)證,準(zhǔn)備,解析
一、加載(Loading)
由類加載器完成,類的class文件讀入內(nèi)存后,并將其保存到方法區(qū)內(nèi),然后就會(huì)創(chuàng)建一個(gè)java.lang.Class類型的對(duì)象。
類被載入JVM中,同一個(gè)類就不會(huì)再次被載入。
需要區(qū)分的是“加載”和“類加載”的區(qū)別,其中加載只是類加載的第一個(gè)環(huán)節(jié)。
加載階段:
- 通過一個(gè)類的全限定名來獲取定義此類的二進(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è)代表此類的java.lang.Class的對(duì)象,作為訪問這個(gè)類的入口。
二、驗(yàn)證(Verification)
目的在于確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,保證被加載類的正確性,不會(huì)危害虛擬機(jī)自身安全,主要驗(yàn)證包括:
- 驗(yàn)證文件格式:第一階段要驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)正確處理。
- 元數(shù)據(jù)的驗(yàn)證:第二階段對(duì)字節(jié)碼描述的信息進(jìn)行語義分析,比如說驗(yàn)證這個(gè)類是不是有父類,類中的字段方法是不是和父類沖突等等,以保證其描述的信息符合Java語言的規(guī)范要求。
- 字節(jié)碼驗(yàn)證:第三個(gè)階段主要是將對(duì)類的方法體(數(shù)據(jù)流和控制流)進(jìn)行驗(yàn)證分析。這個(gè)階段保證方法在運(yùn)行時(shí)不會(huì)出現(xiàn)危害虛擬機(jī)安全的行為語言規(guī)范
- 符號(hào)引用驗(yàn)證:第四個(gè)階段符號(hào)引用驗(yàn)證可以看做是對(duì)類自身以外的信息進(jìn)行匹配性校驗(yàn),發(fā)生時(shí)機(jī)是虛擬機(jī)將符號(hào)引用轉(zhuǎn)換成直接引用時(shí)。
三、準(zhǔn)備(Preparation)
為類的靜態(tài)變量(static )分配內(nèi)存并為其賦零值(默認(rèn)值0、0.0、false、null等),但是不包含用final修飾的static,因?yàn)閒inal在編譯時(shí)就已經(jīng)分配了。
不會(huì)為實(shí)例變量分配初始值,類變量會(huì)分配在方法區(qū)中,而實(shí)例變量是會(huì)隨著對(duì)象一起分配到堆中。
需注意:
- 這里僅只是給靜態(tài)變量賦值,而不是成員變量。
- 在JDK8之前,類的元信息、常量池、靜態(tài)變量等都存儲(chǔ)在永久代這種具體實(shí)現(xiàn)中,而在JDK8及以后字符串常量池、靜態(tài)變量被移除“方法區(qū)”,轉(zhuǎn)移到了堆中而元信息,運(yùn)行時(shí)常量池這些依然保留在方法區(qū)內(nèi),但是具體的存儲(chǔ)方式改成了元空間。
四、解析(Resolution)
將常量池中的符號(hào)引用替換為直接引用(內(nèi)存地址)的過程, 主要包括四種類型引用的解析:類或接口的解析、 字段解析、方法解析、接口方法解析。
- 符號(hào)引用:一個(gè)Java類被編譯成Class之后,如上圖,當(dāng)Test1中引用了Test2,那么在編譯階段,Test1是不知道Test2有沒有被編譯,也代表Test2一定沒有被加載,所以Test1肯定不知道Test2的實(shí)際地址。此時(shí)在Test1的class的文件中,將使用一個(gè)字符串來代表Test2的地址,這個(gè)字符串就被稱為是符號(hào)引用。
- 直接引用:在運(yùn)行時(shí),如果Test1發(fā)生了類加載,到解析階段發(fā)現(xiàn)Test2還未被加載,這時(shí)將會(huì)觸發(fā)Test2的類加載,將Test2加載到虛擬機(jī)中,此時(shí)Test1中Test2的符號(hào)引用將會(huì)被替換為Test2的實(shí)際地址。
在解析階段,會(huì)將常量池中符號(hào)引用替換為直接引用。但是只是替換了部分。這一部分是包含,所有私有方法、靜態(tài)方法、構(gòu)造器及初始化方法都是采用靜態(tài)綁定機(jī)制,在編譯器階段就已經(jīng)指明了調(diào)用方法在常量池中的符號(hào)引用,JVM運(yùn)行的時(shí)候只需要進(jìn)行一次常量池解析即可。如果Test1調(diào)用的Test2是一個(gè)具體的實(shí)現(xiàn)類那么就稱為靜態(tài)解析,因?yàn)榻馕龅哪繕?biāo)類很明確。
那么假如上層Java代碼中使用了多態(tài),這里的Test2可能是一個(gè)抽象類或者是接口,那么Test2就可能有兩個(gè)具體的實(shí)現(xiàn)類Test3和Test4,這時(shí)會(huì)因?yàn)門est2的具體實(shí)現(xiàn)并不明確導(dǎo)致不知道使用哪個(gè)具體類的直接引用來進(jìn)行替換,所以這里就會(huì)一直等到運(yùn)行過程中發(fā)生了調(diào)用,JVM才會(huì)調(diào)用棧中將會(huì)得到的具體的類型信息,這個(gè)時(shí)候在進(jìn)行解析就能用明確的直接引用來替換符號(hào)引用,這時(shí)解析階段就會(huì)發(fā)生在初始化階段之后,這就是動(dòng)態(tài)解析 用它來實(shí)現(xiàn)了后期綁定。
五、 初始化
初始化,則是為標(biāo)記為常量值的字段賦值的過程。只對(duì)static修飾的變量或語句塊進(jìn)行初始化。 如果初始化一個(gè)類的時(shí)候,其父類尚未初始化,則優(yōu)先初始化其父類。 如果同時(shí)包含多個(gè)靜態(tài)變量和靜態(tài)初始化塊,則按照自上而下的順序依次執(zhí)行。
類加載器
Java中默認(rèn)提供的三種類加載器:
- 啟動(dòng)類加載器 BootstrapClassLoader(根加載器): 加載Java_Home/jre/lib目錄下的核心API
- 擴(kuò)展類加載器 ExtClassLoader: 負(fù)責(zé)加載Java_Home/jre/lib/ext目錄下的所有jar包;
- 應(yīng)用類加載 AppClassLoader:繼承URLClassLoader。對(duì)應(yīng)加載的應(yīng)用程序classpath目錄下的所有jar和class等
雙親委派機(jī)制
當(dāng)一個(gè)類加載器收到類加載請(qǐng)求的時(shí)候,它首先不會(huì)自己去加載這個(gè)類的信息,而是把該請(qǐng)求委派給父類加載器,依次向上。
所以所有的類加載請(qǐng)求都會(huì)被委派到父類加載器中,只有當(dāng)父類加載器中無法加載到所需的類,子類加載器才會(huì)自己嘗試去加載該類。
如果當(dāng)前類加載器和所有父類加載器都無法加載該類時(shí),則會(huì)拋出ClassNotFoundException異常。
雙親委派的作用
1、防止重復(fù)加載同一個(gè).class,通過委托確認(rèn)是否加載,如已加載,無需重復(fù)加載,保證數(shù)據(jù)安全。
2、防止核心.class不能被篡改。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何對(duì)spring框架的搭建進(jìn)行封裝--springboot
這篇文章主要介紹了如何對(duì)spring框架的搭建進(jìn)行封裝--springboot,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03解決Springboot整合shiro時(shí)靜態(tài)資源被攔截的問題
這篇文章主要介紹了解決Springboot整合shiro時(shí)靜態(tài)資源被攔截的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01Spring?Cloud?Gateway服務(wù)網(wǎng)關(guān)限流問題及解決
這篇文章主要介紹了Spring?Cloud?Gateway服務(wù)網(wǎng)關(guān)限流問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04response.sendRedirect()實(shí)現(xiàn)重定向(頁面跳轉(zhuǎn))
在Java web開發(fā)中,使用response.sendRedirect()可實(shí)現(xiàn)重定向功能。本文將介紹如何使用該方法進(jìn)行頁面跳轉(zhuǎn),以及該方法的使用場(chǎng)景和注意事項(xiàng),感興趣的可以了解一下2023-04-04關(guān)于Hibernate的一些學(xué)習(xí)心得總結(jié)
Hibernate是一個(gè)優(yōu)秀的Java 持久化層解決方案,是當(dāng)今主流的對(duì)象—關(guān)系映射(ORM)工具2013-07-07Spring Boot console log 格式自定義方式
這篇文章主要介紹了Spring Boot console log 格式自定義方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07spring boot項(xiàng)目導(dǎo)入依賴后代碼報(bào)錯(cuò)問題的解決方法
這篇文章主要給大家介紹了關(guān)于spring boot項(xiàng)目導(dǎo)入依賴后代碼報(bào)錯(cuò)問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08Java8中用foreach循環(huán)獲取對(duì)象的index下標(biāo)詳解
這篇文章主要給大家介紹了關(guān)于Java8中用foreach循環(huán)獲取對(duì)象的index下標(biāo)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04