深入JVM(一):JVM運行時內存結構

記得18年第一次讀周志明著的《深入理解Java虛擬機:JVM高級特性與最佳實踐》讓我有種醐醍灌頂的感覺,後又讀了幾遍,每次都感覺自己受益匪淺,但還是像之前說的一樣,能說出來的纔是自己的。因此,這也是爲什麼雖然網上現在有很多JVM,我卻還堅持寫的原因。要真正理解JVM,還是推薦看這本書。

首先,最好的永遠是官方文檔。有關JVM的內存結構的官方文檔:Chapter 2. The Structure of the Java Virtual Machine2.5之前的可以忽略不看,講的是Java中的數據類型,以及它們的取值範圍,佔用字節數大小等。
2.5 的 Run-Time Data Areas,就是JVM運行時數據區域了,也就是這篇文章的重點,Java運行時的內存結構。

這篇文章主要也是參考官方文檔和《深入理解Java虛擬機》編寫的。

首先總覽一下運行時的數區域。

運行時數據區域

總覽圖
該圖是網上找的,侵權刪。

我直接就按上圖的順序來介紹了。

  1. 方法區(Method Area)
  2. 堆(Heap)
  3. 虛擬機棧(Java Virtual Machine Stacks)
  4. 程序計數器(The pc Register)
  5. 本地方法棧(Native Method Stacks)
  6. 還有一個官方文檔中說的, 運行時常量池(Run-Time Constant Pool),雖然不在上述的五個中,但是也很重要,它是屬於方法區的。

方法區(Method Area)

方法區,首先明確它是線程共享的,它是已編譯代碼的存儲區域,類似於操作系統進程的“文本”碎片。它存儲已被加載的類信息,運行時常量池,靜態變量,常量,即時編譯器編譯後的代碼等數據。

方法區在最開始虛擬機開啓的時候創建。方法區在邏輯上是堆的一部分,但是一般會把它和堆區分開。可以選擇不進行垃圾收集或壓縮。方法區可以是固定大小的,也可以是伸縮的,需要的時候變大,不需要就收縮。同時,方法區的物理存儲位置不需要連續。

該區域在內存不足時會拋出OOM(OutOfMemory)異常。

運行時常量池(Run-Time Constant Pool)

由於運行時常量池是方法區的一種,因此,就放在它下面講了。class文件中除了有類的版本信息、字段、方法,接口等信息,還有就是常量池(constant pool),它包含幾種類型的常量(所以叫常量池),範圍爲從編譯期生成的各種數值常量到運行時使用的字段和字段引用。

堆(Heap)

堆,也是線程共享的,它是所有存放類實例和數組的地方,也是GC的主要區域。

雖然堆中區域並不細分,但是一般還是會按GC分爲:新生代,老年代,其中新生代,又會被分成一個Eden區和兩個From Survivor區和To Survivor區等。

和方法區一樣,在最開始虛擬機開啓的時候創建。它的物理存儲位置不需要連續,堆的大小也是可以固定或伸縮。也會在內存空間不足時拋出OOM異常

虛擬機棧(Java Virtual Machine Stacks)

虛擬機棧,與方法區和堆不同,是線程私有的,它與線程同一時間創建,生命週期與線程相同。在每個方法被調用的時候都會創建一個frame,這個frame用於存儲動態鏈接,方法出口(方法返回值、捕獲異常),每個frame有自己的局部變量表操作數棧,以及當前方法所屬類的運行時常量池的引用

解釋一下一些名詞吧。

frame

通常叫棧幀,它在方法被調用的時候創建,在方法結束或者調用其它方法時停用,一個線程中活動的棧楨只有一個(稱爲當前幀),即正在執行的一個,不過想也想得到,一個線程不可能同時執行兩個方法。在方法結束之後,如果有返回值的話,會將其結果返回給前一個棧楨,然後銷燬。

局部變量表

其實就是一個存儲局部變量的數組,這個數組的長度在編譯時就能確認,數組中每個value,存放的是各種基本數據類型,引用,或者returnAddress。其中long和double會佔用連續的兩個value。有一個實用的場景,可以更深刻的刻畫印象。在方法被調用的時候,參數的傳遞就是使用這個數組。

操作數棧

這個還是可以顧名思義的,包含一個後進先出(LIFO)堆棧。它的最大深度在編譯時就確定了,一般將常量或變量從局部變量或字段加載到操作數堆棧上。其他指令從操作數堆棧中獲取操作數,對它們進行操作,並將結果push回操作數堆棧。操作數棧還用於準備傳遞給方法的參數以及接收方法結果。

程序計數器(The pc Register)

程序計數器,是線程私有的,佔用的內存也比較小。具體作用可看,官方給的解釋:

If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine’s pc register is undefined.

如果是Java方法,那麼記錄的是正在執行的虛擬機字節碼指令地址,如果是Native方法,那麼爲Undefined。

它也是唯一不會拋出OOM異常的。

本地方法棧(Native Method Stacks)

它和虛擬機棧類似,區別只在於虛擬機棧爲Java方法,而本地方法棧爲虛擬機使用到的Native方法服務。

總結

其實篇幅也不大,簡單的總結一下吧。

  1. 線程私有: 程序計數器,虛擬機棧和本地方法棧。
    線程共享:方法區、堆。
    線程私有是不會引起線程安全問題的(這不廢話)。
  2. OOM(OutOfMemory)異常,OOM異常就是內存溢出,簡單來說就是剩餘的內存小於需要的內存導致的。說到內存溢出,就必須提一下內存泄漏了,簡單的說,內存泄漏是應該釋放的內存沒有釋放。內存泄漏有一個比較經典的例子:ThreadLocal,這裏就不展開講ThreadLocal了。內存泄漏通常會導致內存溢出,畢竟內存就那麼大,你佔着塊位置又不釋放,久而久之可用內存就會越來越小,可不就內存溢出了嗎。

OK,這篇文章就到這裏。

最後,謝謝觀看。本人才疏學淺,如有錯誤之處,歡迎指正,共同進步。

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