Java進階之類加載的完整過程(類的生命周期)

1. 概述

虛擬機把描述Java類的數(shù)據從Class文件加載到內存,并對數(shù)據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。這個過程就是類加載的過程。

2.類加載的過程

類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Useing)、卸載(Unloading)7個階段。其中驗證、準備和解析3個部分統(tǒng)稱為連接(Linking),這7個階段的發(fā)生順序如圖所示。

類的生命周期

2.1 加載

“加載”是“類加載”過程的一個階段,由類加載器來完成,主要做以下3件事:

  1. 通過一個類的全限定名來獲取此類的二進制字節(jié)流。
  2. 將這個二進制字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據接口。
  3. 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據的訪問入口。

加載階段完成后, 在內存中實例化一個java.lang.Class類的對象( 并沒有明確規(guī)定是在Java堆中, 對于HotSpot虛擬機而言, Class對象比較特殊, 它雖然是對象, 但是存放在方法區(qū)里面)

Class對象在JDK1.7時放在方法區(qū)(Method Area)中,在JDK1.8時放在堆(Heap)中
Class實例在堆中還是方法區(qū)中?

2.2 驗證

驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的二進制字節(jié)流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。

主要進行如下4個驗證:

  1. 文件格式驗證
    驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當前版本的虛擬機處理。
  2. 元數(shù)據驗證
    對字節(jié)碼描述的信息進行語義分析,以確保其描述的信息符合Java語言規(guī)范的要求。
  3. 字節(jié)碼驗證
    通過數(shù)據流和控制流分析確定成行語義是合法的、符合邏輯的。
  4. 符號引用驗證
    對類自身以外的信息(常量池中的各種符號引用)進行匹配性校驗。

2.3 準備

準備階段是正式為類變量分配內存并設置類變量初始值的階段, 這些變量所使用的內存都將在方法區(qū)中進行分配。這個階段有兩個容易產生混淆的概念。

  1. 這個階段進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。
  2. 這里所說的初始化“通常情況”是指初始化為該數(shù)據類型的零值。
    比如,一個類變量的定義為:public static int value=456;那變量value在準備階段過后的初始值為0而不是456,因為這時候尚未開始執(zhí)行任何Java方法,而把value賦值為456的putstatic指令是程序被編譯后,存放于類構造器< clinit> ()方法之中, 所以把value賦值為456的動作將在初始化階段才會執(zhí)行。
零值列表

對于初始化時,也會有一些“特殊情況”。如果類字段的字段屬性表中存在ConstantValue屬性,那在準備階段變量value就會被初始化為ConstantValue屬性所指定的值。比如,一個類變量的定義為:public static final int value=456;
編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將value賦值為456。

2.4 解析

解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程

2.5 初始化

類初始化階段是類加載過程的最后一步,在這一階段才開始執(zhí)行類中定義的Java代碼(字節(jié)碼)。初始化階段是執(zhí)行類構造器 <clinit> ()方法(類構造器就是“類變量的賦值動作+靜態(tài)語句塊”)的過程。

對于初始化階段,虛擬機嚴格規(guī)范了有且只有5中情況下,必須對類進行初始化:

  1. 當遇到 new 、 getstatic、putstatic或invokestatic 這4條直接碼指令時,比如 new 一個類,讀取一個靜態(tài)字段(未被 final 修飾)、或調用一個類的靜態(tài)方法時。
  2. 使用 java.lang.reflect 包的方法對類進行反射調用時 ,如果類沒初始化,需要觸發(fā)其初始化。
  3. 初始化一個類,如果其父類還未初始化,則先觸發(fā)該父類的初始化。
  4. 當虛擬機啟動時,用戶需要定義一個要執(zhí)行的主類 (包含 main 方法的那個類),虛擬機會先初始化這個類。
  5. 當使用 JDK1.7 的動態(tài)動態(tài)語言時,如果一個 MethodHandle 實例的最后解析結構為 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且這個句柄沒有初始化,則需要先觸發(fā)器初始化。

類初始化的時機詳解

關于<clinit> ()方法:

  1. <clinit>()方法也叫類的構造器,是由編譯器自動收集類中的“所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)”中的語句合并而成,編譯器收集的順序是由語句在源文件出現(xiàn)的順序而決定。靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量,可以賦值,但是不能訪問。
public class test {
    static {
        i = 0; // 賦值動作可以正常編譯
        System.out.println(i);// 進行使用時,編譯器會提示“Illegal forward reference”
    }
    static int i = 1;
}
  1. <clinit>()執(zhí)行順序
    <clinit>()方法與類的構造函數(shù)(或者說實例構造器<innt>() 方法)不同,它不需要顯式地調用父類構造器(類的構造函數(shù)需要顯示使用super.xxx()顯示調用父類的構造函數(shù)),虛擬機會保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經執(zhí)行完畢,即父類的靜態(tài)語句塊優(yōu)先于子類的靜態(tài)語句塊執(zhí)行。
  2. <clinit>()方法對于類和接口來說不是必須的,如果一個類沒有"類變量的賦值動作和靜態(tài)語句塊",那么編譯器可以不為這個類生成<clinit>()方法。
  3. 接口中不能使用靜態(tài)語句塊,但仍有變量初始化的賦值操作(接口中的變量默認為“public static”),因此接口與類一樣都會生成<clinit>()方法。但接口與類不同的是,接口的<clinit>()方法執(zhí)行之前,不需要要執(zhí)行父接口的<clinit>()方法,只有使用父接口的變量時才會執(zhí)行初始化。同理,接口的實現(xiàn)類在初始化時也一樣不會執(zhí)行接口的<clinit>()方法。
  4. <clinit>()線程安全
    虛擬機會保證一個類的<clinit>()方法在多線程環(huán)境中被正確地加鎖、 同步, 如果多個線程同時去初始化一個類, 那么只會有一個線程去執(zhí)行這個類的<clinit>()方法, 其他線程都需要阻塞等待, 直到活動線程執(zhí)行<clinit>()方法完畢。

3. 總結

類的生命周期有七個階段

  1. 加載:通過ClassLoader加載class文件字節(jié)碼,生成Class對象。
  2. 校驗:校驗加載字節(jié)碼的安全性和正確性。
  3. 準備:為類變量分配內存并設置類變量初始的零值。
  4. 解析:將常量池內的符號引用替換為直接引用。
  5. 初始化:將類進行初始化,執(zhí)行類構造器(類變量賦值和靜態(tài)語句塊)。
    5. 初始化:將類進行初始化,執(zhí)行類構造器(靜態(tài)語句塊),可能執(zhí)行類的構造函數(shù)(如果new對象的話先執(zhí)行類構造器,再執(zhí)行類的構造函數(shù))。“new對象會執(zhí)行類構造器”此時還是類加載過程,“再執(zhí)行類的構造函數(shù)”此時好像已經脫離類加載過程,到了對象的創(chuàng)建過程了。這個地方概念還是有點模糊,待我把對象的創(chuàng)建過程復習一下,再來總結?。?!
  6. 使用:使用這個類進行相關操作。
  7. 卸載:不用了,被JVM垃圾回收了。

父子類靜態(tài)代碼塊、代碼塊、構造函數(shù)初始化順序

參考

《深入理解Java虛擬機》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 前面一篇講解了類加載的時機,現(xiàn)在來看看 類加載的過程 是怎樣的。 目錄 一、加載二、驗證三、準備四、解析五、初始化...
    panning閱讀 503評論 0 0
  • 一、類加載的時機 類從被加載到虛擬機內存中開始,到卸載出內存為止,其生命周期包括:加載、驗證、準備、解析、初始化、...
    我愛謙謙閱讀 389評論 0 0
  • 聲明:本文摘抄自《深入理解Java虛擬機》一書,本文完全為自我學習,請感興趣的同學購買正版,支持原創(chuàng) 加載 “加載...
    Chengyu_l閱讀 381評論 0 0
  • 類的生命周期如下圖所示 類加載的全過程包括加載,驗證,準備,解析,初始化這五個階段。本篇文章我們來了解Java虛擬...
    小杰的快樂時光閱讀 696評論 0 0
  • 上篇文章提到過,類加載一共七步驟:加載、驗證、準備、解析、初始化、使用、卸載。現(xiàn)在講解前五步驟。 一、加載在類加載...
    編程界的小學生閱讀 628評論 0 3

友情鏈接更多精彩內容