JVM內存結構

按照Java虛擬機規範的規定, JVM自動管理的內存將包括以下幾個運行時的數據區域:

JVM內存結構

下面分別對幾個數據區域進行說明:

1.程序計數器

程序計數器是JVM中一塊較小的內存區域, 保存著當前線程執行的虛擬機字節碼指令的內存地址.

Java多線程的實現, 其實是通過線程間的輪流切換並分配處理器執行時間的方式實現的, 在任何時刻, 處理器都只會執行一個線程的指令. 在多線程場景下, 為了保證線程切換回來後, 還能恢復到原來的狀態, 找到原先執行的指令, 所以每個線程都會設立一個程序計數器, 並且各個線程之間互不影響, 程序計數器為"線程私有"的內存區域.

若當前線程正在執行的是Java方法, 則程序計數器保存的是虛擬機字節碼的內存地址, 若正在執行的Native方法(非Java方法), 則程序計數器為空.

程序計數器是唯一一個在Java規範中沒有規定任何OutOfMemory(內存耗盡)場景的區域

2.虛擬機棧

虛擬機棧和線程是緊密聯繫的, 每創建一個線程就會對應創建一個Java棧, 生命週期和線程相同, 所有虛擬機棧也是"線程私有"的內存區域.

這個棧中對應多個棧幀, 每調用一個方法就會往棧中創建並壓入一個棧幀, 棧幀是用來存儲方法數據和部分過程結果的數據結構, 每一個方法從調用到最終返回結果的過程, 就對應一個棧幀從入棧到出棧的過程.

線程運行過程中, 只有一個棧幀處於活躍狀態, 被稱為"當前活動幀棧", 當前活動幀棧始終是虛擬機棧的棧頂元素.

在Java虛擬機規範中, 對這個區域規定了兩種異常情況:

  1. 若線程請求的棧深度太深, 超出了虛擬機所允許的深度, 就會出現StackOverFlowError(比如無限遞歸)
  2. 虛擬機棧可以動態擴展, 若擴展到無法申請足夠的內存空間, 就會出現OOM

3.本地方法棧

本地方法棧和虛擬機棧的作用相似, 只是虛擬機棧是為Java方法服務的, 而本地方法棧是為Native方法服務的.

4.方法區

方法區是用來存儲類結構信息(包括常量池、靜態變量、構造函數等)的地方, 類型信息是由類加載器在類加載是從類文件中提取出來的

方法區存在著垃圾回收, 因為用戶通過自定義加載器加載的一些類同樣會成為垃圾, JVM會回收一個未被引用類所佔的空間, 以使方法區的空間最小

方法區是線程共享的.

5.堆

堆是存儲java實例或者對象的地方, 是GC的主要區域, 同樣也是線程共享的內存區域.


下面舉幾個例子來說明:

JVM內存結構

JVM內存結構

上面的main方法中運行過程如下:

  1. 用戶創建了一個Student對象, 運行時JVM首先到方法區尋找該對象的類型信息, 沒有則使用類加載器將Student.class字節碼文件加載至6內存中的方法區, 並將Student類的類型信息存放至方法區
  2. 然後JVM在堆中為新的Student實例分配內存空間, 這個實例持有著指向方法區的Student類型信息的引用(類型信息在方法區中的內存地址)
  3. 在運行的JVM進程中, 會首先運行一個線程執行此用戶程序, 而創建線程的同時也創建了一個虛擬機棧, 虛擬機棧用來跟蹤線程運行中的一系列方法調用過程, 每調用一個方法就會創建一個棧幀並壓入棧中. 上面的stu是對Student的引用, 存放在棧中, 指向堆中Student實例的內存地址.
  4. JVM根據stu引用持有的堆中對象地址, 定位到堆中的Student實例, 由堆中實例指向的方法區Student類型信息引用, 獲得add()方法的字節碼信息, 然後就可以執行add()方法的指令了

總結

一、方法區、堆是線程共享的。 虛擬機棧、本地方法棧和程序計數器是線程私有的

二、棧中:

  1. 每個線程有一個虛擬機棧, 棧中保存基礎數據類型的對象和自定義對象的引用, 對象實例存放在堆區中
  2. 方法的形式參數, 方法調用玩後從棧空間收回
  3. 引用對象的地址, 引用完後, 棧空間地址立即被回收, 堆空間等待GC

三、堆中:

  1. 存儲的全是對象實例, 每個對象包含一個與之對象的class信息, 存放在方法區
  2. JVM只有一個堆區, 被所有線程共享, 堆區中不存放基本類型和對象引用, 只存放對象實例

四、方法區中:

  1. 存放線程執行的字節碼指令
  2. 被所有線程共享, 其中包含所有的class和static變量
  3. 常量池位於方法區中, 例如String的常量池


分享到:


相關文章: