1. 程式人生 > >一文了解Java虛擬機器的重要組成

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

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

一、JAVA記憶體結構

Java虛擬機器規範中規定的JVM執行時資料區如下圖所示:

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

1.方法區

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

2.堆

是JVM中最大的一塊記憶體區域,該區域的目的只是用於儲存物件例項及陣列。該區域也是GC的最主要區域。

3.虛擬機器棧

每個執行緒方法在執行時都會建立一個棧幀,包含區域性變量表、返回地址、運算元棧等資訊。每個方法的執行與完成就對應的棧幀的入棧與出棧過程 。區域性變量表佔用空間的大小在編譯期就確定了。

4.本地方法棧

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

5.程式計數器

是一個小的記憶體空間,如果執行緒正在執行的是一個java方法,則此記憶體區域記錄正在執行的虛擬機器位元組碼指令;如果執行緒正在執行的是native方法,則計算器中的值為空。

二、JAVA垃圾回收機制

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

分代收集演算法的核心思想是將記憶體區域按照物件的生存週期階段進行劃分,其中將堆區劃分為新生代(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指定的類

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

雙親委派模型的工作過程是:如果一個類載入器收到了類的載入請求,會首先把請求委派給自己的父類,每個層次的類載入器都會如此,因為所有的載入請求最終都會發送到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 ,顯示虛擬機器的執行緒快照

Java高架構師、分散式架構、高可擴充套件、高效能、高併發、效能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分散式專案實戰學習架構師視訊免費學習加群:835638062 點選連結加入群聊【Java高階架構】:https://jq.qq.com/?_wv=1027&k=5S3kL3v