JVM系列之Java記憶體結構詳解

我又不是架構師發表於2017-11-22

Java記憶體結構詳解

相信大多數Javaer對Java的記憶體結構都有一定的瞭解,但如果對於Java的記憶體結構只停留的"堆","棧"中顯然是不夠的。今天來給大家詳細談一談Java的記憶體區域結構,本文基於 JDK7 的記憶體結構做講解,JDK8的記憶體結構加上了metaspace,有些許變動,想詳細瞭解的同學請自行翻閱相關資料。

文章結構

  1. 記憶體結構圖
  2. 根據記憶體結構圖各個區域做詳細講解

1 . 記憶體結構圖

圖片說明

  • 方法區,堆區(標綠)為所有執行緒共享的記憶體區域,虛擬機器棧,本地方法棧,程式計數器(標藍)為執行緒似有的記憶體區域,即執行緒隔離的。

2 . 各個區域詳解

程式計數器

程式碼的執行是有順序的,但當CPU在多執行緒間切換時,當從A執行緒切換到B執行緒,再切回到A執行緒時,CPU如何知道該從A執行緒的哪裡繼續執行呢?CPU工作時就是根據每個執行緒的程式計數器的值來選取下一條需要執行的位元組碼指令,即"找到它離開時的位置來繼續執行"。需要提示的是,當CPU執行的是一個Java方法時,程式計數器記錄的是正在執行的虛擬機器位元組碼指令的地址。如果執行的是Native方法,這個計數器值為Undefined,即不發揮作用。

虛擬機器棧

虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀用於儲存區域性變數表,運算元棧,動態連結,方法出口等資訊。每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧和出棧的過程。
區域性變數表存放了編譯期間可以知道大小的各種型別變數,它所需要的記憶體空間大小在編譯期間就已經分配,當一個方法被呼叫時,棧幀進入虛擬機器棧,在執行期間,區域性變數表大小是不會變化的。

本地方法棧

本地方法棧與虛擬機器棧鎖發揮的作用是非常相似的,它們之間的區別不過是虛擬機器棧為虛擬機器執行Java方法服務,而本地方法棧為虛擬機器執行Native方法服務。需要注意的是,由於虛擬機器規範對於本地方法棧的具體實現沒有做強制要求,所以Sun HotSpot直接把本地方法棧和虛擬機器棧合二為一。

Java堆

Java堆是Javaer需要重點關注的一塊區域,因為涉及到記憶體的分配(new關鍵字,反射等)與回收(回收演算法,收集器等),這裡不做太多詳細的介紹記憶體的分配與回收,後期有時間專門出部落格講解。在Java虛擬機器規範中的描述是:所有物件例項以及陣列都要在堆上分配。需要特別注意的是,執行緒共享的Java堆中可能分出多個執行緒私有的分配緩衝區(TLAB,這是為了併發分配記憶體時的髒分配問題,需要使用相關引數來開啟。虛擬機器預設使用CAS加上失敗重試機制解決髒分配問題)。此外,Java堆在HotSpot中的實現是可擴充套件的。引數-Xmx/-Xms來控制。

方法區(永久代)

方法區用於儲存已經被虛擬機器載入的類資訊,常量("zdy","123"等),靜態變數(static變數)等資料。方法區有一個別名叫永久代(Permanent Generation),這是因為HotSpot設計團隊把GC分代收集擴充套件至方法區,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分記憶體,能夠省錢專門為方法區編寫記憶體管理程式碼的工作。對於其他虛擬機器(J9)等,是沒有永久代這個概念的。
永久代的配置引數: -XX:MaxPermSize

執行時常量池

執行時常量池是方法區的一部分,用於存放編譯期生成的各種字面量("zdy","123"等)和符號引用。執行時常量池具有動態性,並非只有Class檔案中的內容才能進入執行時常量池,執行期間也能將新的常量放入池中。如String.intern()方法。

附加--直接記憶體

直接記憶體不是Java虛擬機器規範的記憶體區域。但是這部分也被Javaer頻繁使用,而且也會導致OutOfMember異常。所以這裡順帶提一提。
在JDK4中加入的NIO,使用了Native函式庫直接分配堆外記憶體,然後通過一個快取在Java堆中的Buffer來指向這塊地址進行操作。這樣能夠避免Java堆和Native堆來回複製資料,在某些場景可以顯著提高效能。
直接記憶體不受任何虛擬機器引數控制,但很明顯,你不能大於實體記憶體大小。

結語

JDK7的記憶體模型就大致介紹完了,JVM系列部落格後期將帶給大家更加深入的內容,下期預告:JVM系列之實戰記憶體溢位異常

相關文章