Java 工程師必須掌握的 JVM 類加載機制!
掃描二維碼
隨時隨地手機看文章
這篇文章不聊別的,專門來侃侃JVM的類加載機制
概念
類加載器把class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中,存放在方法區(qū),然后在堆區(qū)創(chuàng)建一個java.lang.Class對象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
一、加載:
查找并加載類的二進制數(shù)據(jù)(把class文件里面的信息加載到內(nèi)存里面)
二、連接:
把內(nèi)存中類的二進制數(shù)據(jù)合并到虛擬機的運行時環(huán)境中
驗證:確保被加載的類的正確性,包括:
類文件的結(jié)構(gòu)檢查:檢查是否滿足Java類文件的固定格式
語義檢查:確保類本身符合Java的語法規(guī)范
字節(jié)碼驗證:確保字節(jié)碼流可以被Java虛擬機安全的執(zhí)行。字節(jié)碼流是操作碼組成的序列。每一個操作碼后面都會跟著一個或者多個操作數(shù)。字節(jié)碼檢查這個步驟會檢查每一個操作碼是否合法。
二進制兼容性驗證:確保相互引用的類之間是協(xié)調(diào)一致的。
準備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認值
解析:把類中的符號引用轉(zhuǎn)化為直接引用(比如說方法的符號引用,是由方法名和相關(guān)描述符組成。在解析階段,JVM把符號引用替換成一個指針,這個指針就是直接引用,它指向該類的該方法在方法區(qū)中的內(nèi)存位置)
三、初始化:
為類的靜態(tài)變量賦予正確的初始值。當(dāng)靜態(tài)變量的等號右邊的值是一個常量表達式時,不會調(diào)用static代碼塊進行初始化。只有等號右邊的值是一個運行時運算出來的值,才會調(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、運行時包:
由同一個類加載器加載并且擁有相同包名的類組成運行時包
只有屬于同一個運行時包的類,才能訪問包可見(default)的類和類成員。
其作用是限制用戶自定義的類冒充核心類庫的類去訪問核心類庫的包可見成員
6、加載兩份相同class對象的情況:A 和 B 不屬于父子類加載器關(guān)系,并且各自都加載了同一個類。
特點:
全盤負責(zé):當(dāng)一個類加載器加載一個類時,該類所依賴的其他類也會被這個類加載器加載到內(nèi)存中。
緩存機制:所有的Class對象都會被緩存,當(dāng)程序需要使用某個Class時,類加載器先從緩存中查找,找不到,才從class文件中讀取數(shù)據(jù),轉(zhuǎn)化成Class對象,存入緩存中。
兩種類型的類加載器
1、 JVM自帶的類加載器(3種)
(1) 根類加載器(Bootstrap):
a、
C++編寫的,程序員無法在程序中獲取該類
b、負責(zé)加載虛擬機的核心庫,比如java.lang.Object
c、沒有繼承ClassLoader類
(2) 擴展類加載器(Extension):
a、Java編寫的,從指定目錄中加載類庫
b、父加載器是根類加載器
c、是ClassLoader的子類
d、如果用戶把創(chuàng)建的jar文件放到指定目錄中,也會被擴展加載器加載。
(3) 系統(tǒng)加載器(System)或者應(yīng)用加載器(App):
a、Java編寫的
b、父加載器是擴展類加載器
c、從環(huán)境變量或者class.path中加載類
d、是用戶自定義類加載的默認父加載器
e、是ClassLoader的子類
2、 用戶自定義的類加載器:
(1)Java.lang.ClassLoader類的子類
(2)用戶可以定制類的加載方式
(3)父類加載器是系統(tǒng)加載器
(4)編寫步驟:
A、繼承ClassLoader
B、重寫findClass方法。從特定位置加載class文件,得到字節(jié)數(shù)組,然后利用defineClass把字節(jié)數(shù)組轉(zhuǎn)化為Class對象
(5)為什么要自定義類加載器?
可以從指定位置加載class文件,比如說從數(shù)據(jù)庫、云端加載class文件
加密:Java代碼可以被輕易的反編譯,因此,如果需要對代碼進行加密,那么加密以后的代碼,就不能使用Java自帶的ClassLoader來加載這個類了,需要自定義ClassLoader,對這個類進行解密,然后加載。
PS:
問題:Java程序?qū)︻惖膱?zhí)行有幾種方式:
1、 主動使用(6種情況):
// JVM必須在每個類“首次主動使用”的時候,才會初始化這些類。
(1) 創(chuàng)建類的實例
(2) 讀寫某個類或者接口的靜態(tài)變量
(3) 調(diào)用類的靜態(tài)方法
(4) 同過反射的API(Class.forName())獲取類
(5) 初始化一個類的子類
(6) JVM啟動的時候,被標明啟動類的類(包含Main方法的類)
// 只有當(dāng)程序使用的靜態(tài)變量或者靜態(tài)方法確實在該類中定義時,該可以認為是對該類或者接口的主動使用。
2、 被動使用:除了主動使用的6種情況,其他情況都是被動使用,都不會導(dǎo)致類的初始化。
3、 JVM規(guī)范允許類加載器在預(yù)料某個類將要被使用的時候,就預(yù)先加載它。
如果該class文件缺失或者存在錯誤,則在程序“首次 主動使用”時,才報告這個錯誤(Linkage Error錯誤)
如果這個類一直沒有被程序“主動使用”,就不會報錯。
類加載機制與接口:
當(dāng)Java虛擬機初始化一個類時,不會初始化該類實現(xiàn)的接口。
在初始化一個接口時,不會初始化這個接口父接口。
只有當(dāng)程序首次使用該接口的靜態(tài)變量時,才導(dǎo)致該接口的初始化。
ClassLoader:調(diào)用Classloader的loadClass方法去加載一個類,不是主動使用,因此不會進行類的初始化。
類的卸載:
有JVM自帶的三種類加載器(根、擴展、系統(tǒng))加載的類始終不會卸載。因為JVM始終引用這些類加載器,這些類加載器使用引用他們所加載的類,因此這些Class類對象始終是可到達的。
由用戶自定義類加載器加載的類,是可以被卸載的。
作者:Huangy遠 ,本文版權(quán)歸作者所有
https://segmentfault.com/a/1190000016415939
特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!