Java虛擬機器-執行時資料區

小海子l發表於2020-12-13

簡圖

在這裡插入圖片描述

執行緒私有區域:虛擬機器棧、本地方法棧和程式計數器
執行緒共享區域:堆和方法區(元空間區)

執行緒私有區域

程式計數器

作用:讀取程式計數器的值來選取下一條位元組碼指令,並完成分支、迴圈、跳轉、異常處理、執行緒回覆等

  • 程式計數器是一個很小的內容空間,可以看做是當前執行緒所執行的位元組碼的行號指示器。在Java虛擬機器的概念模型中,位元組碼直譯器工作是通過改變這個計時器的值來選取下一條需要執行的位元組碼指令,他是程式控制流的指示器,分支、迴圈、跳轉、異常處理、執行緒回覆等基礎功能都需要依賴這個計數器來完成

  • 每個執行緒都有一個獨立的執行緒計數器,各條執行緒之間計時器互不影響,獨立儲存。生命週期與執行緒的生命週期保持一致

  • 如果執行緒正在執行的是一個Java方法,程式計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行的是本地方法(呼叫C類庫執行)程式計數器為空(undefined)

  • 它是唯一一個在《Java虛擬機器規範》中沒有規定任何OutOfMemoryError情況的區域

編譯如下程式碼後使用javap反編譯工具檢視位元組碼檔案Test.class

public class Test {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        System.out.println(a + b);
    }
}
//    指令:javap -v Test.class

在反編譯結果中找到main()方法
在這裡插入圖片描述

途圖中紅框內的數字為指令地址,執行引擎通過讀取指令地址完成方法的執行

虛擬機器棧

作用:在Java程式執行期間,它儲存方法的區域性變數、部分結果,並參與方法的呼叫和返回

  • 每個方法執行的時候,Java虛擬機器都會同步建立一個棧幀(Stack Frame)用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊,對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程
  • 屬執行緒私有區域
  • 生命週期與執行緒生命週期相同
  • 此區域規定了兩類異常狀況:如果執行緒請求的棧深度大於虛擬機器的所允許的深度,將丟擲StackOverFlowError異常;如果Java虛擬機器棧容量可以動態擴充套件,當棧擴充套件時無法申請到足夠的記憶體會丟擲OutOfMemoryError異常
  • 使用-Xss設定棧的大小;棧的大小直接決定了函式呼叫的最大可達深度
  • 棧的深度通常只有幾百K,但是它決定了函式呼叫深度;如果程式使用了很深的遞迴函式,而棧空間非常小,此時很有可能發生棧溢位(java.lang.StackOverflowError)
  • 對於多執行緒任務,應縮小棧,反之
虛擬機器棧的特點:
  1. 是一種快速有效的分配儲存方式,訪問速度僅次於程式計數器

  2. JVM直接對Java棧的操作只有兩個:

    1. 方法的執行對應入棧
    2. 方法結束執行對應出棧
  3. 虛擬機器棧不存在垃圾回收問題

調整棧的大小

通過在Idea中設定-Xss引數,觀察程式碼的執行情況

  • 設定棧大小為128k,當棧深度為967時丟擲棧溢位異常

在這裡插入圖片描述

  • 設定棧大小為256k,當棧深度為2250時丟擲棧溢位異常

在這裡插入圖片描述

棧幀的內部結構
  • 區域性變數表

    • 定義為一個數字陣列,主要用於儲存方法引數和定義在方法體內的區域性變數,這些資料型別包括八種基本資料型別、物件引用,以及returnAddress型別
    • 區域性變數表建立線上程的棧上,是執行緒私有資料,因此不存在資料安全問題
    • 區域性變數表所需的容量大小是編譯期確定下來的,在執行期間不會改變區域性變數表的大小
      在這裡插入圖片描述
  • 運算元棧

    在方法執行過程中,根據位元組碼指令,往棧中寫入資料或提取資料,即入棧和出棧。使用javap指令可檢視運算元棧的深度

在這裡插入圖片描述

  • 動態連結

每一個棧幀內部都包含一個指向執行時常量池中該幀所屬方法的引用。包含這個引用的目的就是為了當前方法的程式碼能夠實現動態連結。動態連結的作用是將符號引用轉換為呼叫方法的直接引用。
在這裡插入圖片描述

  • 方法返回地址

    存放呼叫該方法的的PC暫存器的值 ;程式正常退出時返回該值,異常退出時不返回該值

  • 一些附加資訊

本地方法棧

本地方法棧用於管理本地方法的呼叫(c或c++),會丟擲StackOverFlowErrorOutOfMemoryError

執行緒共享區域

  • 一個Java程式對應一個JVM例項,一個JVM例項存在一個執行時資料區,多個執行緒共享堆和方法區
  • JVM啟動時堆被建立且空間大小以確定,是JVM 管理的最大的一個記憶體區域
    • 堆的大小是可調節的
  • 堆可以處於物理不連續,但在邏輯上應該是視為連續的
  • 所有的執行緒共享堆,還可以劃分出執行緒私有緩衝區(TLAB)
  • 大多數物件例項和陣列都分配在堆上
  • 陣列和物件可能永遠不會儲存在棧上,棧幀中儲存引用,引用指向堆中的位置
  • 方法執行結束後,堆中的物件不會立即移除,在垃圾收集的時候才會被移除
  • 此區域是GC的主要工作區域
細分堆記憶體
  • Java7及之前
    • 年輕代
    • 老年代
    • 永久代
  • Java8及之後
    • 年輕代
      • Eden Space、Survivor0、Survivor1
    • 老年代 :Old Gen
    • 元空間:Metaspace
設定堆空間的的大小

-Xms :memory start; 用來設定堆(年輕代+老年代)的初始記憶體大小

-Xmx:memory max;用來設定堆(年輕代+老年代)的最大記憶體大小

-XX:+PrintGCDetails 輸出GC資訊

年輕代和老年代

在這裡插入圖片描述

  • 使用-XX:NewRatio調整年輕代和老年代的比例,例如:-XX:NewRatio=3,表示年輕代佔1份,老年代佔3份;對應四分之一和四分之三
  • 預設情況下-XX:NewRatio=2
  • 年輕代中的分配比例為8:1:1,使用-XX:-UseAdaptiveSizePolicy關閉自適應記憶體分配策略
  • 使用-Xn設定年輕代的空間

方法區

參考資料

  1. 【Java虛擬機器探究】5.常用JVM配置引數-棧的分配引數
  2. 深入理解Java虛擬機器-周志明(第二版)

相關文章