面試連環call
- Java類是如何被載入到記憶體中的?
- Java類的生命週期都有哪些階段?
- JVM載入的class檔案都有哪些來源?
- JVM在載入class檔案時,何時判斷class檔案的格式是否符合要求?
類生命週期
一個類從被載入到虛擬機器記憶體開始,到解除安裝出記憶體為止,它的整個生命週期將會經歷載入、驗證、準備、解析、初始化、使用和解除安裝七個階段,其中驗證、準備、解析三個部分統稱為連線。
- 載入、驗證、準備、初始化和解除安裝這五個階段的順序是確定的,型別的載入過程必須按照這種順序按步就班地開始,
- 解析階段順序則不固定,它可以在初始化之前,而在某些情況下也可以在初始化階段之後,這是為了支援Java語言的執行時繫結特性。
類載入過程
系統載入 Class 型別的檔案主要三步:載入->連線->初始化。連線過程又可分為三步:驗證->準備->解析。
載入
載入是類載入過程的第一個階段,在載入階段,虛擬機器需要完成以下三件事情
- 透過一個類的全限定名來獲取其定義的二進位制位元組流。
- 將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。
- 在Java堆中生成一個代表這個類的java.lang.Class物件,作為對方法區中這些資料的訪問入口。
載入.class檔案的方式
- 從本地檔案系統中直接載入
- 透過網路下載.class檔案
- 從zip,jar等歸檔檔案中載入.class檔案
- 從專有資料庫中提取.class檔案
- 將Java原始檔動態編譯為.class檔案
連結
驗證
確保被載入的類的正確性
驗證是連線階段的第一步,這一階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
- 檔案格式驗證(Class 檔案格式檢查)
- 後設資料驗證(位元組碼語義檢查)
- 位元組碼驗證(程式語義檢查)
- 符號引用驗證(類的正確性檢查)
準備
為類的靜態變數分配記憶體,並將其初始化為預設值
- 這時候進行記憶體分配的僅包括類變數(
static
),而不包括例項變數,例項變數會在物件例項化時隨著物件一塊分配在Java堆中。 - 這裡所設定的初始值通常情況下是資料型別預設的零值(如
0
、0L
、null
、false
等),而不是被在Java程式碼中被顯式地賦予的值。
注意:JDK 7 之前,HotSpot 使用永久代來實現方法區的時候,類變數所使用的記憶體都應當在 永久的 中進行分配。 而在 JDK 7 及之後,HotSpot 已經把原本放在永久代的字串常量池、靜態變數等移動到堆中,這個時候類變數則會隨著 Class 物件一起存放在 Java 堆中。
解析
虛擬機器將常量池內的符號引用替換為直接引用的過程
解析動作主要針對類或介面、欄位、類方法、介面方法、方法型別、方法控制代碼和呼叫限定符 7 類符號引用進行
- 符號引用
- 符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能夠無歧義的定位到目標即可
- 直接引用
- 直接指向目標的指標(比如,指向Class型別、類變數、類方法的直接引用可能是指向方法區的指標)
- 相對偏移量(比如,指向例項變數、例項方法的直接引用都是偏移量)
- 一個能間接定位到目標的控制代碼
類的初始化
當一個JVM在啟動之後,其中可能包含的類非常多,是不是每個類都會被初始化呢?答案是否定的。
JVM對類的初始化是一個延遲機制,當一個類在首次使用的時候才會被初始化,在同一個執行時package下,一個Class只會被初始化一次。
《Java虛擬機器規範》則是嚴格規定了有且只有6種情況下必須立即對類進行初始化(而載入、驗證、準備自然需要在此之前開始)
-
透過new關鍵字會導致類的初始化
-
訪問類的靜態變數,包括讀取和更新會導致類的初始化
-
訪問類的靜態方法,也會導致類初始化
-
初始化子類會導致父類被初始化
-
對某個類進行反射操作。會導致類被初始化
-
啟動類,就是執行main函式所在的類會導致該類被初始化
注意:構造某個類的陣列時並不會導致該類的初始化
初始化步驟
- 如果這個類還沒有被載入和連結,那先進行載入和連結
- 如果這個類存在直接父類,且這個類還沒有被初始化,那就初始化直接的父類
- 執行類中初始化語句(如static變數和static塊)
參考內容
-
《深入理解 Java 虛擬機器》
-
十分鐘搞懂Java類載入
-
Java 類載入機制
-
類載入過程詳解
-
JVM:類載入過程