一、能學到什麼?
- 虛擬機如何加載Class文件?
- Class文件中的信息進入虛擬機後會發生什麼變化?
二、什麼是類加載機制?
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
三、類的生命週期
類從被加載到虛擬機內存開始,到卸載出內存爲止。
整個生命週期包括:加載、驗證、準備、解析、初始化、使用、卸載7個階段。其中驗證、準備、解析3個部分統稱爲連接。
加載-驗證-準備-初始化必須按照嚴格的先後順序開始(注意是開始)。而解析在某些情況下可能會在初始化之後進行。
四、各個階段做了什麼事
java虛擬機中類加載的全過程:加載、驗證、準備、解析和初始化這5個階段所執行的具體工作。
1、加載
在加載階段,虛擬機需要完成以下三件事情:
- 通過類型的完全限定名,產生一個代表該類型的二進制數據流(根本沒有指明從哪裏獲取、怎樣獲取)
- 將這個字節流所代表的的靜態存儲結構轉化爲方法區的運行時數據結構。
- 在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據結構的訪問入口。
2、驗證
驗證是連接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。
從整體上看,驗證階段大致上會完成4個階段的校驗工作:文件格式、元數據、字節碼、符號引用。
3、準備
準備階段是正式爲 類變量 分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行。
需要注意的兩個地方:
- 首先,這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一起分配在Java堆中。
- 其次,這所說的初始值“通常情況”下是數據類型的零值。
假設一個類變量的定義爲:
public static int value = 123;
那變量value在準備階段過後的初始值是 0 而不是 123,因爲這個時候尚未開始執行任何Java方法,而把value賦值爲123的 putstatic 指令是在程序被編譯後,存放於類構造器 ()方法中,所以把 value 賦值爲 123 的動作將是在初始化階段才進行的。
特殊情況:
對於:
public static final int value = 123;
編譯時 Javac 將會爲 value 生成 ConstantValue(定值)屬性,在準備階段虛擬機就會根據 ConstantValue 的設置將 value 賦值爲 123。
4、解析
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。
符號引用與直接引用有什麼關聯呢?
符號引用(Symbolic References): 符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用於虛擬機實現的內存佈局無關,引用的目標不一定已經加載到內存中。
直接引用(Direct References): 直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。如果有了直接引用,那引用的目標必定已經存在於內存中。
另外:
虛擬機規範沒有規定解析階段發生的具體時間,虛擬機實現可以根據需要來判斷到底是在類被加載時就對常量池中的符號引用進行解析,還是等到一個符號引用將要被使用前纔去解析。
5、初始化
類初始化階段是類加載的最後一步,前面的類加載過程中,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其餘動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的java程序代碼(或者說是字節碼)。
五、什麼時候加載&初始化?
Java虛擬機規範沒有指明一個類需要在什麼時候加載
但是指明瞭當且僅當如下情況會觸發類的初始化。
有且只有以下五種情況必須立即對類進行初始化。
- 使用new關鍵字實例化對象的時候、讀取或設置一個類的靜態字段的時候,已經調用一個類的靜態方法的時候。
- 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有初始化,則需要先觸發其初始化。
- 當初始化一個類的時候,如果發現其父類沒有被初始化就會先初始化它的父類。
- 當虛擬機啓動的時候,用戶需要指定一個要執行的主類(就是包含main()方法的那個類),虛擬機會先初始化這個類;
- 使用Jdk1.7動態語言支持的時候的一些情況。
六、類加載器
從Java開發人員的角度來看,絕大部分Java程序都會使用到以下3種系統提供的類加載器。
啓動類加載器(Bootstrap ClassLoader):
這個類加載器負責將存放在\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。
擴展類加載器(Extension ClassLoader):
這個加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。
應用程序類加載器(Application ClassLoader):
這個類加載器由sun.misc.Launcher$AppClassLoader實現。由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
七、雙親委派模型
我們的應用程序都是由這3種類加載器互相配合進行加載的,如果有必要,還可以加入自己定義的類加載器。
類加載器之間的這種層次關係,稱爲類加載器的雙親委派模型。
雙親委派模型的工作過程: 如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,因此所有的類加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(在它的 搜索範圍 中沒有找到所需的類)時,子類加載器纔會嘗試自己去加載。
使用雙親委派模型的好處:
java類隨着它的類加載器一起具備了一種帶有優先級的層次關係。(不會出現重複類的問題)