JVM系列(一)——java的類加載機制

    前言:以前零零碎碎學習過不少jvm相關的知識,因爲平時工做業務代碼並不會涉及過多jvm底層原理,因此不少知識點看了又忘,忘了又看。此次終於下定決心再來擼一遍,並寫下此jvm系列文章。筆者才疏學淺,不少內容經過看書和查閱他人博客獲取,在此感謝分享! java

1.何爲類的加載? 數據庫

    類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構。 緩存

    類加載器不須要等到某個類被「首次主動使用」時再加載它,JVM規範容許類加載器在預料某個類將要被使用時就預先加載它,若是在預先加載的過程當中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤),若是這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤。 安全

2.類的生命週期 網絡

    加載——驗證——準備——解析——初始化——使用——卸載 數據結構

類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。在這五個階段中,加載、驗證、準備和初始化這四個階段發生的順序是肯定的,而解析階段則不必定,它在某些狀況下能夠在初始化階段以後開始,這是爲了支持Java語言的運行時綁定(動態綁定或晚期綁定)。 jvm

(1)加載 學習

查找並加載類的二進制數據是類加載過程的第一個階段,在加載階段,虛擬機須要完成如下三件事情:
經過一個類的全限定名來獲取其定義的二進制字節流;
將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;
在Java堆中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口。 加密

相對於類加載的其餘階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動做)是可控性最強的階段,由於開發人員既可使用系統提供的類加載器來完成加載,也能夠自定義本身的類加載器來完成加載。加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,並且在Java堆中也建立一個java.lang.Class類的對象,這樣即可以經過該對象訪問方法區中的這些數據。 spa

(2)驗證 確保被加載的類的正確性

這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。驗證階段大體會完成4個階段的檢驗動做:文件格式驗證;元數據驗證;字節碼驗證;符號引用驗證。

(3)準備 爲類的靜態變量分配內存,並將其初始化爲默認值

準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。對於該階段有如下幾點須要注意:
這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨着對象一塊分配在Java堆中;
這裏所設置的初始值一般狀況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。

(4)解析 把類中的符號引用轉換爲直接引用

解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程,解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。
符號引用就是一組符號來描述目標,能夠是任何字面量。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

(5)初始化

初始化,爲類的靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化。

3.類加載器

父類加載器並非經過繼承關係來實現的,而是採用組合實現的。

    站在Java虛擬機的角度來說,只存在兩種不一樣的類加載器:
啓動類加載器:它使用C++實現(僅限於Hotspot),是虛擬機自身的一部分;
全部其它的類加載器:這些類加載器都由Java語言實現,獨立於虛擬機以外,而且所有繼承自抽象類java.lang.ClassLoader,這些類加載器須要由啓動類加載器加載到內存中以後才能去加載其餘的類。

    站在Java開發人員的角度來看,類加載器能夠大體劃分爲如下三類:
啓動類加載器:Bootstrap ClassLoader,負責加載存放在JDK\jre\lib(JDK表明JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,而且能被虛擬機識別的類庫(如rt.jar,全部的java.開頭的類均被Bootstrap ClassLoader加載)。啓動類加載器是沒法被Java程序直接引用的。
擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的全部類庫(如javax.開頭的類),開發者能夠直接使用擴展類加載器。
應用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者能夠直接使用該類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。

應用程序都是由這三種類加載器互相配合進行加載的,若是有必要,咱們還能夠加入自定義的類加載器。由於JVM自帶的ClassLoader只是懂得從本地文件系統加載標準的java class文件,所以若是編寫了本身的ClassLoader,即可以從特定的場所取得java class,例如數據庫中和網絡中。

    JVM類加載機制:
全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其餘Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入;
父類委託,先讓父類加載器試圖加載該類,只有在父類加載器沒法加載該類時才嘗試從本身的類路徑中加載該類;
緩存機制,緩存機制將會保證全部加載過的Class都會被緩存,當程序中須要使用某個Class時,類加載器先從緩存區尋找該Class,
只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區,因此修改了Class後,必須重啓JVM,程序的修改纔會生效。

4.類的加載

    類加載有三種方式:
命令行啓動應用時候由JVM初始化加載;
經過Class.forName()方法動態加載;
經過ClassLoader.loadClass()方法動態加載。

Class.forName()和ClassLoader.loadClass()區別:
Class.forName():將類的.class文件加載到jvm中以外,還會對類進行解釋,執行類中的static塊;
ClassLoader.loadClass():只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。

5.雙親委派模型

    雙親委派模型的工做流程是:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上, 所以,全部的類加載請求最終都應該被傳遞到頂層的啓動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即沒法完成該加載,子加載器纔會嘗試本身去加載該類。

雙親委派模型意義:
系統類防止內存中出現多份一樣的字節碼;
保證Java程序安全穩定運行。

6.自定義類加載器

    一般狀況下,咱們都是直接使用系統類加載器。可是,有的時候,咱們也須要自定義類加載器。好比應用是經過網絡來傳輸 Java類的字節碼,爲保證安全性,這些字節碼通過了加密處理,這時系統類加載器就沒法對其進行加載,這樣則須要自定義類加載器來實現。自定義類加載器通常都是繼承自ClassLoader類,咱們只須要重寫 findClass 方法便可,最好不要重寫loadClass方法,由於這樣容易破壞雙親委託模式。

相關文章
相關標籤/搜索