JVM 裡面的 stack 與 heap

Photo by Ned Daniels on Unsplash

JVM 裡面的 stack 與 heap

JVM

Java 程式碼在運作時, 會透過 compiler 將程式碼編譯為 *.class 的檔案, 而這個檔案會透過 Java Virtual Machine (JVM) 將其轉為 byte code, 讓作業系統執行運作, 而在 JVM 裏面, 會把 java 的資料類型分為 primitive 與 reference, 這樣設計是考量 JVM 在運行之前希望由 compiler 先將所有資料進行檢查.

Primitive type

primitive 類型支持如, byte (8bits), short(16bits), int(32bits), long(64bits), char(16bits).

Reference type

而 reference 類型有, class, array, interface, 他們的默認值為 null.

JVM Stack

JVM STACK 會隨著 JVM 的 THREAD 建立而跟著建立, THREAD 是執行緒, 最少會有一個 Main 的主要執行序. 每個 THREAD 則會依據程式的運行深度, 而使用 STACK 空間, STACK 空間是私有的, 不能與其他 THREAD 共用. STACK 裏面用來存放的每個操作叫做 FRAMES, 在 IntelliJ debug 模式中可觀察到.

在 THREADS 籤頁中除了看到 main 的主執行緒外, 還有 7 個 GC Slave (記憶體回收) 的執行緒在運行, 跟一堆 JVM 的其他 THREADS.

jvm-thread-and-frame-1

在 FRAMES 階段, 切換到 MAIN THREAD, 可以看到下面灰階的部分有 Lambda 跟 Jupiter 套件的運作紀錄.

jvm-thread-and-frame-2

STACK 在資料結構的定義是一種後進先出的資料結構(LIFO), 這是一個很方便用於資料反轉 (Reverse) 的資料結構. 所以當程式拋出 Exceptions 可以透過 e.printStackTrace() 來取得當前 THREAD 使用 STACK 所有過程.

try {
  // do something   
} catch (Exception e) {
  // 輸出 exception 所記錄的 stack trace
  e.printStackTrace();
}

STACK 的大小

STACK 可以想像成, 當前的 THREAD 要走多深. 在文件所定義的 JVM 規範中, 允許 STACK 動態的增加擴充, 但也允許讓開發者去設定 JVM 的 STACK 大小, 可以使用 java -Xss1M 這樣的設定去啟動 JVM, 宣告 STACK 大小為 1Mb 空間.

STACK 的空間估計, 通常取決於要跑幾個 THREADs, 而這又與 CPUs 的計算能力有關.

# run myApp jar with 1Mb mem.
java -Xss:1M myApp

oracle-statck-operation-1

  • 當 STACK 超過 THREAD 的最大深度時, 會拋出 StackOverflowError, 比如說在無限遞迴的情境.

  • 當 JVM 要擴增 STACK 空間時, 但沒有足夠的記憶體可以拿來擴展 STACK 與 THREAD 時, 會拋出 OutOfMemoryError.

JVM Heap

JVM Heap 是在 JVM 啟動時建立的, 但是屬於 Threads 共用的, 主要用來存放 class, array 類型, 由 GC 機制做回收, HEAP 跟 STACK 一樣, 也是可以由開發者去定義大小. 為了有最佳的效能, 官方頁面設定是說建議 XmsXmx 可設定相同的大小, 若沒有特別指定的話, 預設會使用系統的 25% 實體記憶體做設定, 另外 server mode 與 client mode 也會有所差異.

jvm-heap-xms-setting

Java Visualizer

若想要在 Local 觀察 STACK 與 HEAP 的運作的話, 在 InteiilJ 的環境下可以安裝 java-visualizer, 透過 debug 的 step over 就能確實觀察到 STACK 與 HEAP 的運作.

jvm-visualizer

Reference