JVM 类加载机制深入浅出
從類被加載到虛擬機內存中開始,到卸御出內存為止,它的整個生命周期分為7個階段,加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸御(Unloading)。其中驗證、準備、解析三個部分統稱為連接。
7個階段發生的順序如下:
1. 加載
注意:JVM中的ClassLoader類加載器加載Class發生在此階段
2. 驗證
2.1 文件格式的驗證
2.2 元數據驗證
主要對字節碼描述的信息進行語義分析,保證其描述符合Java語言的要求。
1. 類是否有父類
2. 是否繼承了不允許被繼承的類(final修飾過的類)
3. 如果這個類不是抽象類,是否實現其父類或接口中所有要求實現的方法
4. 類中的字段、方法是否與父類產生矛盾(如:覆蓋父類final類型的字段,或者不符合個則的方法)
2.3 字節碼驗證
最復雜的一個階段。主要目的是通過數據量和控制流分析,確定程序語義是合法的,符合邏輯的。
保證被校驗類的方法在運行時不會做出危害虛擬機安全的事件。
2.4 符號引用驗證
符號引用中通過字符串描述的全限定名是否能找到對應的類。
在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段。
符號引用中的類、字段、方法的訪問性(private、protected、public、default)是否可被當前類訪問。
3、準備
準備階段正式為類變量分配內存并設置初始值階段。
public static int value=123; 初始后為 value=0;
對于static final類型,在準備階段會被賦予正確的值
public static final value=123;初始化為 value=123;
如果是boolean值默認賦值為:false
如果是對象引用默認賦值為:null
…
**注意:
只設置類中的靜態變量(方法區中),不包括實例變量(堆內存中),實例變量是在對象實例化的時候初始化分配值的**
4、解析
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
1. 符號引用:簡單的理解就是字符串,比如引用一個類,java.util.ArrayList 這就是一個符號引用,字符串引用的對象不一定被加載。
2. 直接引用:指針或者地址偏移量。引用對象一定在內存(已經加載)。
5、初始化
**注意:
是線程安全的,執行的線程需要先獲取鎖才能進行初始化操作,保證只有一個線程能執行(利用此特性可以實現線程安全的懶漢單例模式)。**
什么是類裝載器ClassLoader
JVM中的類加載器
雙親委派模型
下圖中展示了類加載器直接的關系和雙親委派模型
從圖中我們發現除啟動類加載器外,每個加載器都有父的類加載器。
雙親委派機制:如果一個類加載器在接到加載類的請求時,它首先不會自己嘗試去加載這個類,而是把這個請求任務委托給父類加載器去完成,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。
從類的繼承關系來看,ExtClassLoader和AppClassLoader都是繼承URLClassLoader,都是ClassLoader的子類。而BootStrapClassLoader是有C寫的,不再java的ClassLoader子類中。
**注意:
從圖中可以看到類加載器間的父子關系不是以繼承的方式實現的,而是以組合關系的方式來復用父加載器的代碼。
如果一個類加載器收到了類加載的請求,它首先會把這個請求委派給父加載器去完成,每一個層次的類加載器都是如此。 **
雙親委派模型的好處
Java類隨著加載它的類加載器一起具備了一種帶有優先級的層次關系。比如,Java中的Object類,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型最頂端的啟動類加載器進行加載,因此Object在各種類加載環境中都是同一個類。如果不采用雙親委派模型,那么由各個類加載器自己取加載的話,那么系統中會存在多種不同的Object類。
破壞雙親委派模型
案例一
雙親委派模型的問題:頂層ClassLoader,無法加載底層ClassLoader的類。
JDK的javax.xml.parsers包中定義了xml解析的類接口
Service Provider Interface SPI 位于rt.jar 即接口在啟動ClassLoader中。而SPI的實現類,可能由第三方提供,AppClassLoader進行加載。
解決思路:可以在線程中放入底層的ClassLoader到Thread. setContextClassLoader()中,然后在頂層ClassLoader中使用Thread.getContextClassLoader()獲得底層的ClassLoader進行加載第三方實現。
案例二
Tomcat中使用了自定ClassLoader,并且也破壞了雙親委托機制。
每個應用使用WebAppClassloader進行單獨加載,他首先使用WebAppClassloader進行類加載,如果加載不了再委托父加載器去加載,這樣可以保證每個應用中的類不沖突。每個tomcat中可以部署多個項目,每個項目中存在很多相同的class文件(很多相同的jar包),他們加載到jvm中可以做到互不干擾。
案例三:
利用破壞雙親委派來java的類熱部署實現(每次修改類文件,不需要重啟服務)。
因為一個Class只能被一個ClassLoader加載一次,否則會報java.lang.LinkageError。當我們想要實現代碼熱部署時,可以每次都new一個自定義的ClassLoader來加載新的Class文件。JSP的實現動態修改就是使用此特性實現。
Class加密實現思路
ClassLoader加載.class文件的方式不僅限于從jar包中讀取,還可以從種地方讀取,因為ClassLoader加載時需要的是byte[]數組.
ClassLoader加載Class文件方式:
1. 從本地系統中直接加載
2. 通過網絡下載.class文件
3. 從zip,jar等歸檔文件中加載.class文件
4. 從專有數據庫中提取.class文件
5. 將Java源文件動態編譯為.class文件
加密實現思路:加載Class文件的方式靈活,我們可以自定義ClassLoader,把加密后的Class文件,在加載Class前先進行解密,然后在通過ClassLoader進行加載。
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點擊這里快速進入簡書
GIT地址:http://git.oschina.net/brucekankan/
點擊這里快速進入GIT
總結
以上是生活随笔為你收集整理的JVM 类加载机制深入浅出的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中的CAS和原子类的实现
- 下一篇: JVM 内存区域大小参数设置