JVM內存結構 JVM的類加載機制


JVM內存結構:
1.java虛擬機棧:存放的是對象的引用(指針)和局部變量
2.程序計數器:每個線程都有一個程序計數器,跟蹤代碼運行到哪個位置了
3.堆:對象、數組
4.方法區:字節流(字節碼文件)所代表的靜態存儲結構轉換為方法區的運行時數據結構
5.常量池:String(字符串常量)
 
 
 

  

JVM的類加載機制:雙親委派模型
啟動類加載器(Bootstrap ClassLoader):主要負責加載JDK\jre\lib目錄中並且能被虛擬機識別的類庫到JVM內存中。
擴展類加載器(Extension ClassLoader):主要負責加載JDK\jre\lib\ext目錄中的類庫到JVM內存中。
應用程序類加載器(Application ClassLoader):負責加載類路徑(Classpath)上所指定的類庫,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
 
  1. protected synchronized Class loadClass(String name, boolean resolve)  
  2.         throws ClassNotFoundException {  
  3.     // 首先檢查該name指定的class是否有被加載  
  4.     Class c = findLoadedClass(name);  
  5.     if (c == null) {  
  6.         try {  
  7.             if (parent != null) {  
  8.                 // 如果parent不為null,則調用parent的loadClass進行加載  
  9.                 c = parent.loadClass(name, false);  
  10.             } else {  
  11.                 // parent為null,則調用BootstrapClassLoader進行加載  
  12.                 c = findBootstrapClass0(name);  
  13.             }  
  14.         } catch (ClassNotFoundException e) {  
  15.             // 如果仍然無法加載成功,則調用自身的findClass進行加載  
  16.             c = findClass(name);  
  17.         }  
  18.     }  
  19.     if (resolve) {  
  20.         resolveClass(c);  
  21.     }  
  22.     return c;  
  23. }  

整個過程其實非常簡單:先檢查是否已經被加載過,如果沒有則調用父加載器的loadClass()方法,如果父加載器為空則默認使用啟動類加載器作為父加載器。如果父類加載器加載失敗,則先拋出ClassNotFoundException,然后再調用自己的findClass()方法進行加載。

 

自定義類加載器:若要實現自定義類加載器,只需要繼承java.lang.ClassLoader 類,並且重寫其findClass()方法即可。


雙親委派模型的工作過程為:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的加載器都是如此,因此所有的類加載請求都會傳給頂層的啟動類加載器,只有當父加載器反饋自己無法完成該加載請求(該加載器的搜索范圍中沒有找到對應的類)時,子加載器才會嘗試自己去加載。

 

能不能自己寫個類叫java.lang.System?

 

為了不讓我們寫System類,類加載采用委托機制,這樣可以保證爸爸們優先,爸爸們能找到的類,兒子就沒有機會加載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統提供的System,自己寫的System類根本沒有機會得到加載。
 
 

 

當類的字節碼文件被ClassLoader加載到內存中后,就開始了它的生命周期。

類從被加載到虛擬機內存中開始,到卸載出內存為止,它的生命周期包括了:加載(Loading)、驗證(Verification)、准備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading)七個階段,其中驗證、准備、解析三個部分統稱鏈接。[加載,鏈接,初始化]

加載(裝載)、驗證、准備、初始化和卸載這五個階段順序是固定的,類的加載過程必須按照這種順序開始,而解析階段不一定;它在某些情況下可以在初始化之后再開始,這是為了運行時動態綁定特性(例如接口只在調用的時候才知道具體實現的是哪個子類)。

1.加載:
1)通過“類全名”來獲取定義此類的二進制字節流

2)將字節流所代表的靜態存儲結構轉換為方法區的運行時數據結構

3)在java堆中生成一個代表這個類的java.lang.Class對象,作為方法區這些數據的訪問入口

 

相對於類加載過程的其他階段,加載階段是開發期可控性最強的階段,因為加載階段可以使用系統提供的類加載器(ClassLoader)來完成,也可以由用戶自定義的類加載器完成,開發人員可以通過定義自己的類加載器去控制字節流的獲取方式。

加載階段完成后,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中。然后在java堆中實例化一個java.lang.Class類的對象,這個對象作為程序訪問方法區中的這些類型數據的外部接口。

 

2.驗證:

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

 

3.准備:

准備階段是正式為類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配。這個階段中有兩個容易產生混淆的知識點,首先是這時候進行內存分配的僅包括類變量(static 修飾的靜態變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一起分配在java堆中。其次是這里所說的初始值“通常情況”下是數據類型的零值,假設一個類變量定義為:

public static int value  = 12;

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

上面所說的“通常情況”下初始值是零值,那相對於一些特殊的情況,如果類字段的字段屬性表中存在ConstantValue屬性,那在准備階段變量value就會被初始化為ConstantValue屬性所指定的值,建議上面類變量value定義為:

public static final int value = 12;

 

 

 

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

這些符號(如int a = 5中的a)就是符號引用,而解析過程就是把它轉換成指向堆中的對象地址的邏輯地址。

5.初始化:

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

1.遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,如果類沒有進行過初始化,則需先觸發其初始化。生成這4條指令的最常見的java代碼場景是:使用new關鍵字實例化對象、讀取或設置一個類的靜態字段(被final修飾、已在編譯器把結果放入常量池的靜態字段除外)的時候,以及調用類的靜態方法的時候。

2.使用java.lang.reflect包的方法對類進行反射調用的時候

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

4.jvm啟動時,用戶指定一個執行的主類(包含main方法的那個類),虛擬機會先初始化這個類

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

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



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