JVM學習——(一)類加載機制&運行時數據區

一、類文件到虛擬機(類加載機制)

1.1 裝載(Load)

類加載的第一步是對類的裝載過程:

1)通過一個類的全限定名獲取定義此類的二進制字節流。

2)將這個字節流代表的靜態存儲結構轉化爲方法區的運行時數據結構

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

 

1.2 鏈接(Link)

類加載的第二步是鏈接,鏈接這一步中包含驗證、準備、解析的步驟。

1.2.1 驗證(Verify

保證被家族類的正確性

文件格式驗證

元數據驗證

字節碼驗證

符號引用驗證

1.2.2 準備(Prepare)

爲類的靜態變量分配內存,並將其初始化爲默認值。

1.2.3 解析(Resolve)

把類中的符號引用轉換爲直接引用。

 

1.3 初始化(Initialize)

類加載的第三步是初始化操作。

對靜態變量、靜態代碼塊執行初始化操作。

1.4 類加載機制圖解

使用和卸載並不算是類加載過程中的階段,只是畫完整了一點。

二、類裝載器(ClassLoader)

在裝載(Load)階段,其中第一步是通過類的全限定類名獲取其定義的二進制字節流,需要藉助類裝載器完成。顧名思義,就是用來裝載Class文件的。

2.1 分類

1)Bootstrap ClassLoader:負責加載$JAVA_HOME中jir/lib/rt.jar 裏所有的class或Xbootclassoath選項指定的jar包。由C++實現,不是ClassLoader的子類。

2)Extension ClassLoader 負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目錄下的jar包。

3)App ClassLoader 負責加載classpath中指定的jar包及 Djava.class.path 所指定目錄下的類和jar包。

4)Custom ClassLoader 通過java.lang.ClassLoader的子類自定義加載class,屬於應用程序根據自身需要自定義的ClassLoader,如tomcatjboss都會根據j2ee規範自行實現ClassLoader

2.2 圖解類裝載器

2.3 雙親委派機制

定義:如果一個類加載器在接到加載類的請求時,它首先不會自己去嘗試加載這個類,而是把這個請求任務委託給父類加載器去完成,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回。只有父類加載器無法完成此加載任務時,才自己去加載。

優勢:Java類隨着加載它的類加載器一起具備了一種帶有優先級的層次關係。

比如,Java中的Object類,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載。因此Object在各種類加載環境中都是同一個類。如果不採用雙親委派模型,由各個類加載器自己去加載的話,那麼系統中會存在多個不同的Object類。

破壞:可以繼承ClassLoader類,然後重寫其中的loadClass方法。

 

三、運行時數據區(Run-Time Data Areas)

在裝載階段的第2、3步可以發現有運行時數據、堆、方法區等名詞。

其實說的就是類文件被類裝載器裝載進來之後,類中的內容(比如變量、常量、方法、對象這些數據)要被存儲起來,存儲的位置肯定是在JVM中有對應的空間。

3.1 圖解

3.2 各部分簡介

3.2.1 Method Area(方法區)

方法區是各個線程共享的內存區域,在虛擬機啓動時創建。

用於存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。

雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,但它又有個別名叫Non-Heap(非堆),目的是和Java堆區分開來。

此時再回看裝載階段的第2步:(2)將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構:
注意:
1)方法區在JDK8就是Metaspace,在JDK6或7中就是Perm Space
2)Run-Time Constant Pool(運行時常量池)
 
Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池,用於存放編譯時期生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池存放。
 
3.2.2 Heap(堆)
Java堆是Java虛擬機所管理內存中最大的一塊,在虛擬機啓動時創建,被所有線程所共享。
Java對象實例、數組都在堆上分配。
此時回看裝載階段的第3步:(3)Java堆中生成一個代表這個類的java.lang.Class對象,作爲對方法區中這些數據的訪問入口:
 
3.2.3 JavaVirtual Macheine Stacks(虛擬機棧)
虛擬機棧是一個線程執行的區域,保存着一個線程中方法的調用狀態。
換句話說,一個Java線程的運行狀態,由一個虛擬機棧來保存。
所以虛擬機棧肯定是線程私有的,隨着線程的創建而創建。
 
每一個被線程執行的方法,爲該棧中的棧幀,也就是每個方法對應一個棧幀。
調用一個方法,就會向棧中壓入一個棧幀;
一個方法調用完成,就會把該棧幀從棧中彈出。
 
3.2.4 The Pc Register(程序計數器)
 
一個JVM進程中有多個線程在執行,而線程中的內容是否能夠擁有執行權,是根據CPU調度來的。
假如線程A正在執行到某個地方,突然失去了CPU的執行權,切換到線程B執行了。然後當線程A再獲得CPU執行權時,怎麼繼續執行呢?
這就是需要在線程中維護一個變量,記錄線程執行到的位置。
 
程序計數器佔用的內存空間很小,由於Java虛擬機的多線程是通過線程輪流切換,並分配處理器執行時間的方式來實現的。
在任何時刻,一個處理器只會執行一條線程中的指令。
因此爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,每條線程需要有一個獨立的程序計數器(線程私有)。
 
如果線程正在執行java方法,則計數器記錄的是正在執行的虛擬機字節碼指令的地址;
如果正在執行的是Native方法,則這個計數器爲空。
 
3.2.5 Native Method Stacks(本地方法棧)
如果當前線程執行的方法是Native類型的,這些方法就會在本地方法棧中執行。
 

四、運行時數據區提煉總結

名稱 是否線程共享 作用 可能出現的異常

方法區

線程共享

存放類信息(版本、字段、方法、接口等)、常量、靜態變量、即時編譯後的代碼等數據。

內存不足時,拋出OutOfMemoryError(內存不足錯誤)。

虛擬機棧

線程私有

存放局部變量表、操作數據棧、動態鏈接、方法出口等信息。

棧幀深度大於允許最大深度時,拋出StackOverflowError(棧溢出錯誤);

內存不足時,拋出OutOfMemoryError(內存不足錯誤)

線程共享

存放對象實例和數組。

內存不足時,拋出OutOfMemoryError(內存不足錯誤)

程序計數器

線程私有

記錄當前線程所執行的字節碼指令。

唯一不會出現OutOfMemoryError的區域。

本地方法棧

線程私有

java虛擬機棧類似,不過是爲了JVM用到的Native方法服務。

棧幀深度大於允許最大深度時,拋出StackOverflowError(棧溢出錯誤);

內存不足時,拋出OutOfMemoryError(內存不足錯誤)

運行時常量池

線程共享

屬於方法區的一部分。

存放編譯器生成的各種字面量和符號引用。

內存不足時,拋出OutOfMemoryError(內存不足錯誤)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章