
這篇文章不聊別的,專門來侃侃JVM的類加載機(jī)制
一、概念
類加載器把class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,存放在方法區(qū),然后在堆區(qū)創(chuàng)建一個java.lang.Class對象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
Ⅰ、加載:
查找并加載類的二進(jìn)制數(shù)據(jù)(把class文件里面的信息加載到內(nèi)存里面)
Ⅱ、連接:
把內(nèi)存中類的二進(jìn)制數(shù)據(jù)合并到虛擬機(jī)的運(yùn)行時環(huán)境中
1.驗證:
確保被加載的類的正確性,包括:
類文件的結(jié)構(gòu)檢查:檢查是否滿足Java類文件的固定格式
語義檢查:確保類本身符合Java的語法規(guī)范
字節(jié)碼驗證:確保字節(jié)碼流可以被Java虛擬機(jī)安全的執(zhí)行。字節(jié)碼流是操作碼組成的序列。每一個操作碼后面都會跟著一個或者多個操作數(shù)。字節(jié)碼檢查這個步驟會檢查每一個操作碼是否合法。
二進(jìn)制兼容性驗證:確保相互引用的類之間是協(xié)調(diào)一致的。
2.準(zhǔn)備:
為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值
3.解析:
把類中的符號引用轉(zhuǎn)化為直接引用(比如說方法的符號引用,是由方法名和相關(guān)描述符組成。在解析階段,JVM把符號引用替換成一個指針,這個指針就是直接引用,它指向該類的該方法在方法區(qū)中的內(nèi)存位置)
Ⅲ、初始化:
為類的靜態(tài)變量賦予正確的初始值。當(dāng)靜態(tài)變量的等號右邊的值是一個常量表達(dá)式時,不會調(diào)用static代碼塊進(jìn)行初始化。只有等號右邊的值是一個運(yùn)行時運(yùn)算出來的值,才會調(diào)用static初始化。
二、雙親委派模型
1、將類加載請求向上傳遞。當(dāng)一個類加載器收到類加載請求時,它首先不會自己去加載這個類的信息,而是把該請求轉(zhuǎn)發(fā)給父類加載器,依次向上。
所以所有的類加載請求都會被傳遞到父類加載器中,只有當(dāng)父類加載器中沒有找到所需的類,子類加載器才會自己嘗試去加載該類。
當(dāng)前類加載器和所有父類加載器都無法加載該類時,拋出ClassNotFindException異常。
2、意義:提高系統(tǒng)的安全性。用戶自定義的類加載器不可能加載應(yīng)該由父加載器加載的可靠類。
比如用戶定義了一個惡意代碼,自定義的類加載器首先讓系統(tǒng)加載器去加載,系統(tǒng)加載器檢查該代碼不符合規(guī)范,于是就不繼續(xù)加載了。
3、定義類加載器:如果某個類加載器能夠加載一個類,那么這個類加載器就叫做定義類加載器
4、初始類加載器:定義類加載器及其所有子加載器都稱作初始類加載器。
5、運(yùn)行時包:
由同一個類加載器加載并且擁有相同包名的類組成運(yùn)行時包
只有屬于同一個運(yùn)行時包的類,才能訪問包可見(default)的類和類成員。
其作用是限制用戶自定義的類冒充核心類庫的類去訪問核心類庫的包可見成員
6、加載兩份相同class對象的情況:A 和 B 不屬于父子類加載器關(guān)系,并且各自都加載了同一個類。
三、特點:
1.全盤負(fù)責(zé):當(dāng)一個類加載器加載一個類時,該類所依賴的其他類也會被這個類加載器加載到內(nèi)存中。
2.緩存機(jī)制:所有的Class對象都會被緩存,當(dāng)程序需要使用某個Class時,類加載器先從緩存中查找,找不到,才從class文件中讀取數(shù)據(jù),轉(zhuǎn)化成Class對象,存入緩存中。
四、兩種類型的類加載器
1、 JVM自帶的類加載器(3種)
(1) 根類加載器(Bootstrap):
- C++編寫的,程序員無法在程序中獲取該類
- 負(fù)責(zé)加載虛擬機(jī)的核心庫,比如java.lang.Object
- 沒有繼承ClassLoader類
(2) 擴(kuò)展類加載器(Extension):
- Java編寫的,從指定目錄中加載類庫
- 父加載器是根類加載器
- 是ClassLoader的子類
- 如果用戶把創(chuàng)建的jar文件放到指定目錄中,也會被擴(kuò)展加載器加載。
(3) 系統(tǒng)加載器(System)或者應(yīng)用加載器(App):
- Java編寫的
- 父加載器是擴(kuò)展類加載器
- 從環(huán)境變量或者class.path中加載類
- 是用戶自定義類加載的默認(rèn)父加載器
- 是ClassLoader的子類
2、 用戶自定義的類加載器:
Java.lang.ClassLoader類的子類
用戶可以定制類的加載方式
父類加載器是系統(tǒng)加載器
-
編寫步驟:
- 繼承ClassLoader
- 重寫findClass方法。從特定位置加載class文件,得到字節(jié)數(shù)組,然后利用defineClass把字節(jié)數(shù)組轉(zhuǎn)化為Class對象
-
為什么要自定義類加載器?
可以從指定位置加載class文件,比如說從數(shù)據(jù)庫、云端加載class文件
加密:Java代碼可以被輕易的反編譯,因此,如果需要對代碼進(jìn)行加密,那么加密以后的代碼,就不能使用Java自帶的ClassLoader來加載這個類了,需要自定義ClassLoader,對這個類進(jìn)行解密,然后加載。
五、PS:
問題:Java程序?qū)︻惖膱?zhí)行有幾種方式:
1、 主動使用(6種情況):
// JVM必須在每個類“首次主動使用”的時候,才會初始化這些類。
- 創(chuàng)建類的實例
- 讀寫某個類或者接口的靜態(tài)變量
- 調(diào)用類的靜態(tài)方法
- 同過反射的API(Class.forName())獲取類
- 初始化一個類的子類
- JVM啟動的時候,被標(biāo)明啟動類的類(包含Main方法的類)
// 只有當(dāng)程序使用的靜態(tài)變量或者靜態(tài)方法確實在該類中定義時,該可以認(rèn)為是對該類或者接口的主動使用。
2、 被動使用:除了主動使用的6種情況,其他情況都是被動使用,都不會導(dǎo)致類的初始化。
3、 JVM規(guī)范允許類加載器在預(yù)料某個類將要被使用的時候,就預(yù)先加載它。
如果該class文件缺失或者存在錯誤,則在程序“首次 主動使用”時,才報告這個錯誤(Linkage Error錯誤)
如果這個類一直沒有被程序“主動使用”,就不會報錯。
類加載機(jī)制與接口:
- 當(dāng)Java虛擬機(jī)初始化一個類時,不會初始化該類實現(xiàn)的接口。
- 在初始化一個接口時,不會初始化這個接口父接口。
- 只有當(dāng)程序首次使用該接口的靜態(tài)變量時,才導(dǎo)致該接口的初始化。
ClassLoader:調(diào)用Classloader的loadClass方法去加載一個類,不是主動使用,因此不會進(jìn)行類的初始化。
類的卸載:
- 有JVM自帶的三種類加載器(根、擴(kuò)展、系統(tǒng))加載的類始終不會卸載。因為JVM始終引用這些類加載器,這些類加載器使用引用他們所加載的類,因此這些Class類對象始終是可到達(dá)的。
- 由用戶自定義類加載器加載的類,是可以被卸載的。
寫在最后
最后,歡迎做Java的工程師朋友們加入Java高級架構(gòu)進(jìn)階Qqun:963944895
群內(nèi)有技術(shù)大咖指點難題,還提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構(gòu)資料)
比你優(yōu)秀的對手在學(xué)習(xí),你的仇人在磨刀,你的閨蜜在減肥,隔壁老王在練腰, 我們必須不斷學(xué)習(xí),否則我們將被學(xué)習(xí)者超越!
趁年輕,使勁拼,給未來的自己一個交代!
