JVM——類的加載過程


附一張圖方便理解,一個類的執行過程

類的加載過程,簡明的來說


  類裝飾器就是尋找類的字節碼文件並構造出類在JVM內部表示的對象組件。在Java中,類裝載器把一個類裝入JVM中,要經過以下步驟:

  1. 裝載:查找和導入Class文件;
  2. 鏈接:執行校驗、准備和解析步驟,其中解析步驟是可以選擇的:
    1. 校驗:檢查載入Class文件數據的正確性;
    2. 准備:給類的靜態變量分配存儲空間;
    3. 解析:將符號引用轉成直接引用;
  3. 初始化:對類的靜態變量、靜態代碼塊執行初始化工作。

  類裝載工作由ClassLoader及其子類負責,ClassLoader是一個重要的Java執行時系統組件,它負責在運行時查找和裝入Class字節碼文件。JVM在運行時會產生三個ClassLoader:根裝載器ExtClassLoader(擴展類裝載器)和AppClassLoader(系統類裝載器)。其中,根裝載器不是ClassLoader的子類,它使用C++編寫,因此我們在Java中看不到它,根裝載器負責裝載JRE的核心類庫,如JRE目標下的rt.jar、charsets.jar等。ExtClassLoader和AppClassLoader都是ClassLoader的子類。其中ExtClassLoader負責裝載JRE目錄ext中的JAR類包;AppClassLoader負責裝載ClassPath路徑下的類包。

關於這三個ClassLoader的詳解博客鏈接

一、加載


  “加載” 是“類加載”過程的一個階段。在加載階段,虛擬機需要完成以下3件事情:

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

  虛擬機規范的這3點要求其實並不算具體,因此虛擬機實現與具體應用的靈活度都是相當大的。

  相對於類加載過程的其他階段,一個非數組類的加載階段(准確地說,是加載階段中獲取類的二進制字節流的動作)是開發人員可控性最強的,因為加載階段既可以使用系統提供的引導類加載器來完成,也可以由用戶自定義的類加載器區完成,開發人員可以通過定義自己的類加載器去控制字節流的獲取方式(即重寫一個類加載器的loadClass()方法)。

  對於數組類而言,數組類本身不通過類加載器創建,它是由Java虛擬機直接創建的。但數組類與類加載器任然有很密切的關系,因為數組類的元素類型最終是要靠類加載器去創建。

  加載階段完成后,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,方法區中的數據存儲格式由虛擬機實現自行定義,虛擬機規范未規定此區域的具體數據結構。然后在內存中實例化一個java.lang.Class類的對象(並沒有明確規定是在Java堆中,對於HotSpot虛擬機而言,Class對象比較特殊,它雖然是對象,但是存放在方法區里面),這個對象將作為程序訪問方法區中的這些類型數據的外部接口。

  加載階段與連接階段的部分內容(如一部分字節碼文件格式驗證動作)是交叉進行的,加載階段尚未完成,連接階段可能已經開始,但這些夾在加載階段之中進行的動作,仍然屬於連接階段的內容,這兩個階段的開始時間仍然保持着固定的先后順序。

二、驗證


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

  Java語言本身是相對安全的語言,使用純粹的Java代碼無法做到注入訪問數組邊界意外的數據、將一個對象轉型為它並未實現的類型、跳轉到不存在的代碼行之類的事情,如果這樣做了,編譯器將拒絕編譯。

  但前面已經說過,Class文件並不一定要求用Java源碼編譯而來,可以使用任何途徑產生,甚至包括用十六進制編輯器直接編寫來產生Class文件。在字節碼語言層面上,上述Java代碼無法做到的事情都是可以實現的,至少語義上是可以表達出來的。

  虛擬機如果不檢查輸入的字節流,對其完全信任的話,很可能會因為載入了有害的字節流而導致系統崩潰,所以驗證是虛擬機對自身保護的一項重要工作。

三、准備


  准備階段是正式為類變量分配內存並設置類變量初始值得階段,這些變量所使用的內存都講在方法區中進行分配。這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一起分配在Java堆中。其次,這里所說的初始值“通常情況”下是數據類型的零值,假設一個類變量的定義為:

public static int value = 123;

  那變量value在准備階段過后的初始值為0而不是123,因為這時候尚未開始執行任何Java方法,而把value賦值為123的putstatic指令是程序被編譯后,存放於類構造器<clinit>()方法之中,所以把value賦值為123的動作將在初始化階段才會執行。

四、解析


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

五、初始化


  類初始化階段是類加載過程的最后一步,前面的類加載過程中,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程序代碼(或者說是字節碼)。

  在准備階段,變量已經賦過一次系統要求的初始值,而在初始化階段,則根據程序員通過程序制定的主觀計划去初始化類變量和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器<clinit()>方法的過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM