1. 程式人生 > 程式設計 >詳解JAVA類載入機制

詳解JAVA類載入機制

1.一段簡單的程式碼

首先來一段程式碼,這個是單例模式,可能有的人不知道什麼是單例模式,我就簡單說一下

單例模式是指一個類有且只有一種物件例項。這裡用的是餓漢式,還有懶漢式,雙檢鎖等等。。。。

寫這個是為了給大家看一個現象

class SingleTon{
  public static int count1;
  public static int count2=0;
  private static SingleTon instance=new SingleTon();
  public SingleTon(){
    count1++;
    count2++;
  }

  public static SingleTon getInstance() {
    return instance;
  }
}
public class JVMTest {
  public static void main(String[] args) {
    SingleTon.getInstance();
    System.out.println(SingleTon.count1);
    System.out.println(SingleTon.count2);
  }
}

執行結果:

詳解JAVA類載入機制

同樣的程式碼我把private static SingleTon instance=new SingleTon()移到上面

詳解JAVA類載入機制

我們再看執行結果:

詳解JAVA類載入機制

可以發現執行結果發生了變化

這個先放在這,等了解了類載入機制過程,後面再說

2.什麼是類載入機制

  概念:Java中的類載入機制指虛擬機器把描述類的資料從 Class 檔案載入到記憶體,並對資料進行校驗、轉換、解析和初始化,最終形成可以被虛擬機器直接使用的 Java 型別。

  大白話:其實就是把位元組碼檔案放在虛擬機器裡面去

  與那些在編譯時需要進行連線工作的語言不同,在Java語言中,型別的載入、連線、初始化都是在程式執行期間完成的,這種策略雖然會令類載入時稍微多一些效能的開銷,但是會為Java應用程式提供高度的靈活性。

  類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括了:載入、驗證、準備、解析、初始化、使用、解除安裝七個階段。驗證、準備、解析被稱為連線

  注意:載入、驗證、準備、初始化、使用、解除安裝這幾個順序是確定的,但是解析不一定,它在某些情況下可以在初始化階段後再開始,主要是為了支援Java語言執行時繫結(只做瞭解即可)

  類載入機制包括前面五個階段。

詳解JAVA類載入機制

3.類載入時機

  什麼情況下虛擬機器需要開始載入一個類呢?虛擬機器規範中並沒有對此進行強制約束,這點可以交給虛擬機器的具體實現來自由把握。

4.類初始化時機

  那麼,什麼情況下虛擬機器需要開始初始化一個類呢?這在虛擬機器規範中是有嚴格規定的,虛擬機器規範指明 有且只有 五種情況必須立即對類進行初始化(而這一過程自然發生在載入、驗證、準備之後):

  遇到new、getstatic、putstatic或invokestatic這四條位元組碼指令(注意,newarray指令觸發的只是陣列型別本身的初始化,而不會導致其相關型別的初始化(比如,new String[]只會直接觸發String[]的初始化,也就是觸發對類java.lang.String的初始化,而直接不會觸發String類的初始化)時,如果類沒有進行過初始化,則需要先對其進行初始化。

生成這四條指令的最常見的Java程式碼場景是:

1 使用new關鍵字例項化物件的時候;
2 讀取或設定一個類的靜態欄位(被final修飾,已在編譯器把結果放入常量池的靜態欄位除外)的時候;
3 呼叫一個類的靜態方法的時候。
  2) 使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒有進行過初始化,則需要先觸發其初始化。

  3) 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。

  4) 當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main()方法的那個類),虛擬機器會先初始化這個主類。

  5) 當使用jdk1.7動態語言支援時,如果一個java.lang.invoke.MethodHandle例項最後的解析結果REF_getstatic,REF_putstatic,REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行初始

化,則需要先出觸發其初始化。

  注意,對於這五種會觸發類進行初始化的場景,虛擬機器規範中使用了一個很強烈的限定語:“有且只有”,這五種場景中的行為稱為對一個類進行 主動引用。除此之外,所有引用類的方式,都不會觸發初始化,稱為 被動引用。

  特別需要指出的是,類的例項化與類的初始化是兩個完全不同的概念:

    類的例項化是指建立一個類的例項(物件)的過程;

5.類載入過程

載入:

載入“是”類加機制”的第一個過程,在載入階段,虛擬機器主要完成三件事:

(1)通過一個類的全限定名(可以理解為絕對路徑)來獲取其定義的二進位制位元組流

(2)將這個位元組流所代表的的靜態儲存結構轉化為方法區的執行時資料結構

(3)在堆中生成一個代表這個類的Class物件,作為方法區中這些資料的訪問入口。

相對於類載入的其他階段而言,載入階段是可控性最強的階段,因為程式設計師可以使用系統的類載入器載入,還可以使用自己的類載入器載入。我們在最後一部分會詳細介紹這個類載入器

驗證

驗證的主要作用就是確保被載入的類的正確性。也是連線階段的第一步。說白了也就是我們載入好的.class檔案不能對我們的虛擬機器有危害,所以先檢測驗證一下。他主要是完成四個階段的驗證:

(1)檔案格式的驗證:驗證.class檔案位元組流是否符合class檔案的格式的規範,並且能夠被當前版本的虛擬機器處理。這裡面主要對魔數、主版本號、常量池等等的校驗(魔數、主版本號都是.class檔案裡面包含的資料資訊、在這裡可以不用理解)。

(2)元資料驗證:主要是對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合java語言規範的要求,比如說驗證這個類是不是有父類,類中的欄位方法是不是和父類衝突等等。

(3)位元組碼驗證:這是整個驗證過程最複雜的階段,主要是通過資料流和控制流分析,確定程式語義是合法的、符合邏輯的。在元資料驗證階段對資料型別做出驗證後,這個階段主要對類的方法做出分析,保證類的方法在執行時不會做出威海虛擬機器安全的事。

(4)符號引用驗證:它是驗證的最後一個階段,發生在虛擬機器將符號引用轉化為直接引用的時候。主要是對類自身以外的資訊進行校驗。目的是確保解析動作能夠完成。

對整個類載入機制而言,驗證階段是一個很重要但是非必需的階段,如果我們的程式碼能夠確保沒有問題,那麼我們就沒有必要去驗證,畢竟驗證需要花費一定的的時間。當然我們可以使用-Xverfity:none來關閉大部分的驗證。

準備:

為類變數分配記憶體,並將其初始化為預設值。(此時為預設值,在初始化的時候才會給變數賦值)即在方法區中分配這些變數所使用的記憶體空間。例如

public static int value = 123;

此時在準備階段過後的初始值為0而不是123;

特例:

public static final int value = 123;

此時value的值在準備階段過後就是123。

解析:

解析階段將類中符號引用轉換為直接引用。符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能夠無歧義的定位到目標即可。

初始化:

初始化階段是執行類構造器<client>方法的過程。<client>方法是由編譯器自動收集類中的類變數的賦值操作和靜態語句塊中的語句合併而成的。虛擬機器會保證<client>方法執行之前,父類的<client>方法已經執行完畢。如果一個類中沒有對靜態變數賦值也沒有靜態語句塊,那麼編譯器可以不為這個類生成<client>()方法。

java中,對於初始化階段,有且只有以下五種情況才會對要求類立刻“初始化”(載入,驗證,準備,自然需要在此之前開始):

1 使用new關鍵字例項化物件、訪問或者設定一個類的靜態欄位(被final修飾、編譯器優化時已經放入常量池的例外)、呼叫類方法,都會初始化該靜態欄位或者靜態方法所在的類。

2 初始化類的時候,如果其父類沒有被初始化過,則要先觸發其父類初始化。

3 使用java.lang.reflect包的方法進行反射呼叫的時候,如果類沒有被初始化,則要先初始化。

4 虛擬機器啟動時,使用者會先初始化要執行的主類(含有main)

5 jdk 1.7後,如果java.lang.invoke.MethodHandle的例項最後對應的解析結果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法控制代碼,並且這個方法所在類沒有初始化,則先初始化。

JVM初始化步驟

1、假如這個類還沒有被載入和連線,則程式先載入並連線該類

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

3、假如類中有初始化語句,則系統依次執行這些初始化語句

6.分析剛開始的問題

第一種情況:

連線階段:為靜態變數賦值初始值,SingleTon=null,count1=0,count2=0

初始化階段:從上到下執行賦值操作和靜態程式碼塊

  count1=0,count2=0建立物件後對兩個值進行遞增結果count1=1,count2=1

第二種情況:

連線階段:為靜態變數賦值初始值,SingleTon=null,count2=0

初始化階段:從上到下執行賦值操作和靜態程式碼塊

  先建立物件後對兩個值進行遞增count1=1,count2=1

  再賦值count1沒變,count2=0

6.類載入器分類

· 啟動類載入器(Bootstrap ClassLoader):

啟動類載入器負責載入存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath引數指定的路徑中的類。

· 擴充套件類載入器(ExtClassLoader):

擴充套件類載入器負責載入JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變數指定的路徑中的所有類庫(如javax.*開頭的類)。

· 應用類載入器(AppClassLoader):

應用類載入器負責載入使用者類路徑(ClassPath)所指定的類,開發者可以直接使用該類載入器。

類載入器的職責:

· 全盤負責:

當一個類載入器負責載入某個Class時,該Class所依賴的和引用的其他Class也將由該類載入器負責載入,除非顯式使用另外一個類載入器來載入。

· 父類委託:

類載入機制會先讓父類載入器試圖載入該類,只有在父類載入器無法載入該類時才嘗試從自己的類路徑中載入該類。

父類委託機制是為了防止記憶體中出現多份同樣的位元組碼,保證java程式安全穩定執行。

· 快取機制:

快取機制將會保證所有載入過的Class都會被快取,當程式中需要使用某個Class時,先從快取區尋找該Class,只有快取區不存在,系統才會讀取該類對應的二進位制資料,並將其轉換成Class物件,存入快取區。這就是為什麼修改了Class後,必須重啟JVM,程式的修改才會生效。

7.雙親委派模型

類載入器 Java 類如同其它的 Java 類一樣,也是要由類載入器來載入的;除了啟動類載入器,每個類都有其父類載入器(父子關係由組合(不是繼承)來實現);

所謂雙親委派是指每次收到類載入請求時,先將請求委派給父類載入器完成(所有載入請求最終會委派到頂層的Bootstrap ClassLoader載入器中),如果父類載入器無法完成這個載入(該載入器的

搜尋範圍中沒有找到對應的類),子類嘗試自己載入。

詳解JAVA類載入機制

雙親委派好處

· 避免同一個類被多次載入;

· 每個載入器只能載入自己範圍內的類;

雙親委派是可以違背的麼?

這個是可以的只需要我們去自定義類載入器重寫ClassLoader中的loadclass方法

以上就是詳解JAVA類載入機制的詳細內容,更多關於JAVA類載入機制的資料請關注我們其它相關文章!