1. 程式人生 > >java虛擬機類

java虛擬機類

語句 完成 前三 tst 方法 形式 如果 初始 字段

java虛擬機中類的加載

技術分享圖片

            (JVM的大致結構圖)

從發class文件到內存中的類,按先後順序,需要經過加載,鏈接以及初始化三大步驟。

java語言的類型可分為兩大類:基本類型(primitive type)和引用類型(references type)

基本類型:是由java虛擬機預先定義好的。

引用類型:Java將其細分為四種,類、接口、數組類和泛型參數。

由於泛型參數會在編譯過程中被擦除(泛型類型在邏輯上可以看成是多個不同的類型,實際上都是相同的基本類型。),因此Java虛擬機實際上只有前三種。在類、接口和數組類中,數組類是由java虛擬機直接生成的,其他兩種則有對應的字節流。

字節流:最常見的形式要屬由Java編譯器生成的class文件。除此之外,我們也可以在程序內部直接生成,或者從網絡中獲取字節流。這些不同形式的字節流,都會被加載到java虛擬機中,稱為類或者接口。

無論是直接生成的數組類,還是加載的類,java虛擬機都要對其進行鏈接和初始化。

加載

加載是指查找字節流,並且據此創建類的過程。對於類來說,java虛擬機需要借助類加載器來完成查找字節流的過程。

當JVM啟動時,會形成由三個類加載器組成的初始類加載器層次結構:

1、啟動類加載器(BootstrapClassLoader):是嵌在JVM內核中的加載器,它是由C++實現的,沒有對應的java對象,因此在java中只能用null來代替。它主要負責加載JAVA_HOME/lib下的類庫,啟動類加載器無法被應用程序直接使用。

除了啟動類加載器外,其他的類加載器都是java.lang.ClassLoader的子類,因此有對應的java對象。這些類加載器需要先由另一個類加載器,比如說啟動了加載器,加載至java虛擬機中,方能執行類加載。

在Java9之前,啟動類加載器負責加載最為基礎,最為重要的類,比如存放在jre的lib目錄下的jar包(以及由虛擬機參數-Xbootclasspath指定的類)。除了啟動類加載器之外,另外兩個重要的類加載器是擴展類加載起(extension class loader)和應用類加載器(application class loader),均由java核心類庫提供。

2、擴展類加載器(extension class loader):其父類是啟動類加載器,他負責加載相對次要、但又通用的類,比如存放在jre的lib/ext目錄下的jar包中的類(以及由系統變量java.ext.dirs指定的類)。

3、應用類加載器(application class loader):其父類是擴展類加載器,它負責加載用用程序下的類。(這裏的應用程序路徑,便是指虛擬機參數-cp/-classpath、系統變量CLASSPATH所制定的路徑。)默認情況下,應用程序中包含的類是由應用類加載器加載的。

java9引入了模塊系統,,並且略微更改了上述的類加載器,擴展類加載器被改名為平臺類加載器(platform class loader)。Java SE 中除了少數幾個關鍵模塊,比如說、java.base是由啟動類加載器加載之類,其他的模塊均由平臺加載器所加載。

除了java核心類庫提供的類加載器之外,我們還可以加入自定義的加載器,來實現特殊的加載方式。舉個例子,我們可以對class文件進行加密,加載時再利用自定義的類加載最其解密。

除了加載功能之外,類加載器還提供了命名空間的作用。在java虛擬機中,類的唯一性是由類加載器實例以及類的全名一同確定的。即便是通一串字節流,經由不同的類加載器加載,也會得到兩個不同的類。在大型應用中,我們往往會借助這一特性,來運行同一個類的不同版本。

鏈接

鏈接是指將創建成的類合並至java虛擬機中,使之能執行的過程。它可以分為驗證、準備及解析三個階段。

驗證:確保被加載的類能夠滿足java虛擬機的約束條件,檢驗被加載的類是否有正確的內部結構,並和其他類協調一致。

準備:為被加載類的靜態字段分配內存,並設置默認初始值。

解析:將類中的二進制數據中的符號引用替換成直接引用(final修改的常量的替換)。

JVM並沒有要你在鏈接過程中完成解析,它僅規定了:如果某些字節碼使用了符號引用,那麽在執行這些字節碼之前,需要完成對這些符號引用的解析。

初始化

在java代碼中如果要初始化一個靜態字段,我們可以在聲明時直接賦值,也可以在靜態代碼塊中對其賦值。

如果直接賦值的靜態字段被final修飾,並且他的類型是基本類型或字符串時,那麽該字段便會被java編譯器標記為常量值(ConstantValue),其初始化直接被java虛擬機完成。除此之外的直接賦值操作以及所有靜態代碼塊中的代碼,則會被java編譯器置於同一方法中,並把它命名為<clinit>。

類加載的最後一步是初始化,便是為標記為常量值的字段賦值,以及執行<clinit>方法的過程。java虛擬機會通過加鎖來確保類的<clinit>方法僅被執行一次。

只有當初始化完成之後,類才正式稱為可執行的狀態。

JVM初始化一個類分為幾步:

1、假如這個類還沒有被加載和鏈接,程序先加載並鏈接這個類。

2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類。

3、假如類中有初始化語句,則依次執行其初始化語句。

類的加載和初始化何時被觸發

1、當虛擬機啟動時,初始化用戶指定的主類。(直接使用java.exe命令來運行某個主類)

2、創建類的實例(new方法)。

3、調用某個類的靜態方法,初始化該靜態方法所在的類。

4、訪問某個類或接口的靜態屬性,初始化該靜態屬性所在的類。

5、使用反射機制來創建某個類或接口的對應java.lang.Class對象(Class.forName("Person")),初始化這個類。

6、子類的初始化會觸發父類的初始化。

7、如果一個接口定義了default方法,那麽直接實現或間接實現該接口的類的初始化,會觸發該接口的初始化。

8、當初次使用MehodHandle實例時,初始化該MehodHandle指向的方法所在的類。

註:此文為極客時間鄭雨迪專欄,java虛擬機講解及自己查資料的學習總結。鄭雨迪《深入拆解Java虛擬機》很不錯。

java虛擬機類