在Java的沙箱中, 類裝載器體系結構的第一道防線。 因為java的位元組碼是由類裝載器裝載入Java虛擬機器的。
不同的類裝載器提供不同的命名空間, 命名空間可以隔離不同的類裝載喊之間裝載的類, 有助於安全的實現。
一、class檔檢驗器
class檔檢驗器保證裝載的class檔內容有正確的結構, 而且這些class檔相互協調一致。
class檔檢驗器只是在位元組碼執行之前對class檔進行一次分析檢驗它的完整性, 遇到跳轉指令都進行檢查, 確認跳轉的指令會跳到另一個合法的指令,
否則, 非法的指令, 導致虛擬機器的崩潰。
class檔檢驗器要進行4趟獨立的掃描來完成它的操作。
1.第一趟:class檔的結構檢查
比如每個class檔都必須固定以4個位元組:
0xcafe babe
這就是java的class檔的魔數, 每個class檔都要以它為開始。
2.第二趟:類型資料的語義檢查
3.第三趟:位元組碼驗證
4.第四趟:符號引用的驗證
Java的類裝載體系
Java虛擬機器通過
一、裝載
二、連接
1.驗證
2.準備
3.(可選的)解析
三、初始化
, 經過這些步驟, 使得一個Java類型可以被正在運行的程式使用, 當然解析是一個例外, 可以在初始化後再解析。
裝載
裝載階段由三個基本動作組成, 要裝載一個類型, Java虛擬機器必須:
1.通過該類型的完全限定名, 產生一個代表該類型的二進位資料流;
2.解析這個二進位資料流為方法區的內部資料結構;
3.創建一個表示該類型的java.lang.Class類的實例。
有了類型的二進位資料後, Java虛擬機器必須對這些資料進行足夠的處理, 然後才創建類java.lang.Class的實例物件, 虛擬機器必須把這些二進位資料解析為與實現相關的內部資料結構。
裝載步驟的最終產品就是Class類的實例物件, 它成為Java程式與內部資料結構之間的介面。
這樣一個過程, 就是把一個類型的二進位資料解析為方法區內中的內部資料結構, 並在堆上建立一個Class物件的過程, 這被為“創建”類型。
比如, 在裝載過程, 虛擬機器必須解析代表類型的二進位資料流。 在解析過程期間, 大多數虛擬機器會檢查二進位資料以確保資料全部是預期的格式。 Java class檔案格式的解析可能檢查魔數, 確保每一個部分在位置, 有正確的長度, 驗證檔不是太長或者太短。 雖然這些檢查在裝載期間完成, 是在正式的連接階段之前進行, 但它們仍然屬於邏輯上的驗證。 檢查被裝載的類型是否有任何問題的整個過程都屬於驗證。
雙親委託載入機制
驗證
在正式的驗證階段首先確保各個類之間二進位相容的檢查:
1.檢查final的類不能擁有子類;
2.檢查final的方法不能被覆蓋;
3.確保在類型和父類型之間沒有不相容的聲明
比如, 兩個方法有相同的名字, 參數在數量、順序和類型都相同, 但返回值卻不相同。 當然檢查類型和父類型, 父類型必須是被初始化的, 所以父類型肯定是已經被裝載的。
4.檢查所有的常量池入口相互之間一致;
5.檢查常量池中的所有的特殊字串(類名、欄位名和方法名、欄位描述符和方法描述符)是否符合格式;
6.檢查位元組碼的完整性。
虛擬機器必須為它執行的每個方法檢驗位元組碼的完整性,
虛擬機器的實現沒有強制在驗證階段進行位元組碼驗證, 比如, 可以自由地選擇在執行每條語句時單獨進行驗證, 不過, Java虛擬機器指令集設計的一個目標就得使得位元組碼流可以一次性驗證位元組流, 而不是在程式執行時動態驗證, 使得Java程式運行速度得到很大的提高。
準備
虛擬機器裝載了一個類型, 並且進行了一系列的選擇的驗證操作之後, 就可以進入準備階段了。
在準備階段, 虛擬機器為類變數分配記憶體, 設置預設值, 在準備階段, 是不會執行Java代碼的, 在準備階段, 虛擬機器把變數新分配的記憶體根據類型設置預設值。
類型 預設值
======================================================
int 0
long 0L
short (short)0
char ' '
byte (byte)0
boolean false
float 0.0f
double 0.0d
reference null
======================================================
在虛擬機器內部, boolean往往用int類型實現表示的,會被默認為0(boolean的false)。
解析
連接經過了驗證和準備兩個階段,可以進入第三個階段--解析,解析過程就是在類型的常量池中尋找類、介面、欄位和方法的符號引用,把這些符號引用替換成直接引用的過程。
初始化
為準備讓一個類或者介面被首次主動使用,最後一個步驟就是初始化,也就是為類變數賦予正確的初始值,這裡的正確初始值,是我們在寫程式時,在程式上顯式地給類變數賦予的值,這裡初始化的值是相對於連接裡準備階段的默認初始值而言的。
在Java代碼中,正確的初始化值是通過類變數初始化語句或者靜態初始化語句賦值的。
class A{
//類變數初始化語句
static int width = 0;
//靜態初始化語句
static {
width = 2;
}
}
所有類變數初始化語句或者靜態初始化語句都被Java編譯器收集在一起,放在一個特殊的方法。對類來說,叫類初始化方法;對介面來說,叫介面初始化方法,在類和介面的class檔中,這個方法被稱為""。Java程式方法是無法調用到""方法的,只能由Java虛擬機器調用。
初始化一個類包含兩個步驟:
1.如果類型存在一個直接父類,而父類還沒初始化的時候,則先初始化父類;
2.如果類存在一個初始化方法"
當然,初始化父類也是要通過這兩個步驟的,所以第一次初始化的類型永遠都是是Object。而初始化介面則不需要先初始化父介面,如果介面有"
虛擬機器必須在類或者介面首次主動使用時初始化,下面是主動使用的情形。
主動使用
主動使用的6種情形:
1.當創建某個類的新實例時(或者通過位元組碼中執行new指令;或者通過不明確地創建、反射、clone或者反序列化);
2.當調用某個類的靜態方法時(即在位元組碼中執行invokestatic指令時);
3.當使用某個類或者介面的靜態欄位時,或者對該欄位賦值時(即在位元組碼中,執行getstatic或者putstatic指令時),用final修改的靜態欄位除外,它被初始化成一個編譯時的常量運算式;
4.當調用Java API中的某些反射方法時,比如類Class中的方法或者java.lang.reflect包中類的方法;
5.當初始化某個類的子類時(某個類初始化時,要求它的父類必須已被初始化了);
6.當虛擬機器實例啟動時,啟動類即main方法的啟動類。
被動使用
使用類或者介面聲明的非常量的靜態欄位都是主動使用。比如,類中聲明的欄位可能會被子類引用;介面中聲明的欄位可能會被子介面或者實現了這個介面類別引用。對於子類、子介面和實現了介面的類來說,這就是被動使用--使用它們不會觸發它們的初始化。只有當欄位是類或者介面聲明的時候才是主動使用。
boolean往往用int類型實現表示的,會被默認為0(boolean的false)。解析
連接經過了驗證和準備兩個階段,可以進入第三個階段--解析,解析過程就是在類型的常量池中尋找類、介面、欄位和方法的符號引用,把這些符號引用替換成直接引用的過程。
初始化
為準備讓一個類或者介面被首次主動使用,最後一個步驟就是初始化,也就是為類變數賦予正確的初始值,這裡的正確初始值,是我們在寫程式時,在程式上顯式地給類變數賦予的值,這裡初始化的值是相對於連接裡準備階段的默認初始值而言的。
在Java代碼中,正確的初始化值是通過類變數初始化語句或者靜態初始化語句賦值的。
class A{
//類變數初始化語句
static int width = 0;
//靜態初始化語句
static {
width = 2;
}
}
所有類變數初始化語句或者靜態初始化語句都被Java編譯器收集在一起,放在一個特殊的方法。對類來說,叫類初始化方法;對介面來說,叫介面初始化方法,在類和介面的class檔中,這個方法被稱為""。Java程式方法是無法調用到""方法的,只能由Java虛擬機器調用。
初始化一個類包含兩個步驟:
1.如果類型存在一個直接父類,而父類還沒初始化的時候,則先初始化父類;
2.如果類存在一個初始化方法"
當然,初始化父類也是要通過這兩個步驟的,所以第一次初始化的類型永遠都是是Object。而初始化介面則不需要先初始化父介面,如果介面有"
虛擬機器必須在類或者介面首次主動使用時初始化,下面是主動使用的情形。
主動使用
主動使用的6種情形:
1.當創建某個類的新實例時(或者通過位元組碼中執行new指令;或者通過不明確地創建、反射、clone或者反序列化);
2.當調用某個類的靜態方法時(即在位元組碼中執行invokestatic指令時);
3.當使用某個類或者介面的靜態欄位時,或者對該欄位賦值時(即在位元組碼中,執行getstatic或者putstatic指令時),用final修改的靜態欄位除外,它被初始化成一個編譯時的常量運算式;
4.當調用Java API中的某些反射方法時,比如類Class中的方法或者java.lang.reflect包中類的方法;
5.當初始化某個類的子類時(某個類初始化時,要求它的父類必須已被初始化了);
6.當虛擬機器實例啟動時,啟動類即main方法的啟動類。
被動使用
使用類或者介面聲明的非常量的靜態欄位都是主動使用。比如,類中聲明的欄位可能會被子類引用;介面中聲明的欄位可能會被子介面或者實現了這個介面類別引用。對於子類、子介面和實現了介面的類來說,這就是被動使用--使用它們不會觸發它們的初始化。只有當欄位是類或者介面聲明的時候才是主動使用。