一文了解JAVA虛擬機的重要組成

JVM是JAVA平臺的重要組成之一,因涉及知識點太多,故從以下幾個方面對JVM進行淺層面的介紹,如果需要深入理解,推薦學習機械工業出版社的《深入理解JAVA虛擬機》。

一、JAVA內存結構

Java虛擬機規範中規定的JVM運行時數據區如下圖所示:

一文了解JAVA虛擬機的重要組成

總體來說,分為線程共享部分(方法區、堆)和線程隔離區(虛擬機棧、本地方法棧和程序計數器)。

1.方法區

用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。其中常量存儲於運行時常量區中,運行時常量區是區的一部分,用於存儲編譯期生成的字面量和符號引用。但運行時常量區的內容並不只是在編譯期間產生,通過String.intern()也可以實現在運行時向常量區中添加內容。

2.堆

是JVM中最大的一塊內存區域,該區域的目的只是用於存儲對象實例及數組。該區域也是GC的最主要區域。

3.虛擬機棧

每個線程方法在執行時都會創建一個棧幀,包含局部變量表、返回地址、操作數棧等信息。每個方法的執行與完成就對應的棧幀的入棧與出棧過程 。局部變量表佔用空間的大小在編譯期就確定了。

4.本地方法棧

與虛擬機棧類似,不過其中執行是本地方法。對於HotSpot虛擬機而言,本地方法棧和虛擬機棧是統一的。

5.程序計數器

是一個小的內存空間,如果線程正在執行的是一個java方法,則此內存區域記錄正在執行的虛擬機字節碼指令;如果線程正在執行的是native方法,則計算器中的值為空。

二、JAVA垃圾回收機制

JAVA的垃圾回收主要涉及到確定對象是否存活、垃圾收集等算法,其中確定對象回收算法採用的是可達性分析算法,垃圾收集目前各JVM廠商廣泛採用的是分代收集算法。這裡面主要描述下分代收集算法的過程。

一文了解JAVA虛擬機的重要組成

分代收集算法的核心思想是將內存區域按照對象的生存週期階段進行劃分,其中將堆區劃分為新生代(young generation)和老年代(old generation)。將非堆區(一般指方法區)劃分為持久代(permanent generation)。

1.新生代

新生代又可再分為Eden區和兩個Survivor區(兩個Survivor區的大小是一樣的,便於交換)。新生成的對象都會先在新生代的Eden區進行保存。新生代的特點是每次垃圾回收都會有大量的內存被回收,而且收集比較頻繁,所以新生代適合如下的收集算法:

首先,新生成的對象分配到Eden區,如果eden區滿了,則將可達性的對象複製到survivor1區,後清空eden區。

然後,如果survivor1區滿了,則將eden區與survivor1區的可達性對象複製到survivor2區,後清空eden區和survivor1區,清空完後將survivor2區與survivor1區交換,即保持survivor2是空的。

再次,如果survivor2區也滿了,則將eden區、survivor1區、survivor2區的可達性對象複製到老年代中,並清空新生代中。

最後,如果老年代也滿了,就觸發full gc了。

2.老年代

老年代的內存比新生代大的多,這個區域執行垃圾回收的頻度不高。當老年代滿時,會觸發full gc。

3.持久代

持久代一般指方法區,該區需要回收的有廢棄的常量和類。對於常量可用可達性分析的方法進行判斷回收,對於類則需要同時滿足以下條件才會被回收:

首先,該類的所有實例對象都已被回收;

其次,該類的類加載器也已被回收;

再次,該類的Class方法沒有在任何地方被引用,即無法通過在任何地方通過反射訪問到該類的方法。

4.什麼時候會解決垃圾回收?

綜上所述,當eden滿時,就會觸發scavenge gc,當出現以下情況時會觸發full gc:

老年代已滿;

持久代已滿;

調用System.gc()方法;

三、JAVA類加載過程

JVM類加載過程具體裝載、驗證、準備、解析、初始化這五個部分。

1.裝載

在裝載過程中,需要完成以下事情:

1)通過類的全限定名獲取類的二進制字節流;

2)將類的二進制字節流轉換為方法區的運行時數據結構;

3)生成一個代表此類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。

2.驗證

驗證、解析和初始化又稱為是連接階段,在驗證驗證主要是確保二進制字節流符合JVM的規範,不會危害計算機的安全。具體驗證階段需要做的事情如下:

1)文件格式驗證,驗證字節流是否符合Class文件格式規範;

2)元數據驗證,對字節碼進行語義驗證,以保證其描述信息符合JAVA語言規範;

3)字節碼驗證,通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的;

4)符號引用驗證,對常量池中的各種符號引用的信息進行匹配性驗證。

3.準備

準備的過程其實是分配內存的過程。在這個階段有兩個容易產生混淆的概念:一是此階段分配內存的只是類變量(static變量),不包含實例變量,實例變量的內存分配是在對象實例化時隨對象一起分配在堆中;二是該階段分配內存中保存的值只是數據類型的零值,具體值需要在初始化階段進行賦值。也有特殊情況,就是對於靜態常量(final修飾)會在準備階段將值賦值為真實值。

4.解析

解析階段就是將常量池內折符號引用轉換為直接引用的過程,具體包括類和接口的解析、字段的解析、方法的解析、接口方法和解析。

5.初始化

初始化階段其實就是執行類構造函數(clinit)的階段。對於clinit()需要說明以下幾點:

1)clinit()中的程序是自動收集類中static變量及static塊產生的,執行順序與代碼中的順序一致。靜態語句塊中只能訪問在其之前聲明的static變量,在其之後聲明的static變量只能賦值,不能訪問。

2)執行clinit()方法前,JVM會自動調用父類的clinit()方法;

3)虛擬機會保證一個類的clinit()在多線程環境中,自動加鎖、同步。

四、JVM的類加載器

JVM的類加載是通過類加載器實現的,常用的類加載器包括下面三種:

1.啟動類加載器(bootstrap classloader):加載{JDK_HOME}/lib下的類

2.擴展類加載器(extension classloader):加載{JDK_HOME}/lib/ext下的類

3.應用程序類加載器(application classloader):加載classpath指定的類

對於不同類加載器以及他們之間的協作可以參考下面的雙親委派模型。

一文了解JAVA虛擬機的重要組成

雙親委派模型的工作過程是:如果一個類加載器收到了類的加載請求,會首先把請求委派給自己的父類,每個層次的類加載器都會如此,因為所有的加載請求最終都會發送到bootstarp加載器中,只有當父加載器確實無法自己完成加載請求時,子加載器才會嘗試自己加載。

雙親委派模型使得JAVA類能夠按層次進行加載,不會造成混亂。

五、JVM的相關工具

JDK中有很多強大的監控工具,可以直接在命令行運行。這對於在生產環境進行監控是非常有用的。例如SUN JDK中就包含了以下監控和故障處理工具。

jps: jvm process status tool,顯示指定系統內所有的hotspot虛擬機進程

jstat: jvm statistics monitoring tool,用於收集hotspot虛擬機各方面的運行數據

jinfo: configuration info for java,顯示虛擬機配置信息

jmap: memory map for java,生成虛擬機的內存轉儲快照(heapdump文件)

jhat: jvm heap dump browser,用於分析heapmap文件,它會建立一個http/html服務器,讓用戶可以在瀏覽器上查看分析結果

jstack: stack trace for java ,顯示虛擬機的線程快照


分享到:


相關文章: