隨著程式碼量增長,系統愈發臃腫、效率下降,身為追求卓越的工程師,除了優化程式結構,記憶體優化與效能調校更是必修課。要對 Java 程式進行記憶體優化與效能調優,不瞭解虛擬機內部原理(或稱規範)是絕對不行的。推薦一本好書《深入 Java 虛擬機 (第二版)》(Bill Venners 著,曹曉剛、蔣靖 譯),本文即為閱讀該書後的個人理解闡述。瞭解 JVM 規範與實現,不僅有助於記憶體優化與效能調優,更能從技術深度協助我們編寫高效、穩定的 Java 代碼。例如掌握記憶體模型與垃圾回收機制,就不會過度依賴 GC,而是在必要時顯式釋放物件引用,降低不必要記憶體消耗;了解 Java 堆疊運作原理,可透過減少遞迴層數、迴圈次數降低堆疊溢位風險。雖應用開發者不一定直接涉及 JVM 底層實作,但這些背景知識會潛移默化地提升程式品質。
本文簡明扼要說明 Java 虛擬機的體系架構與記憶體模型,如有用詞不當或解釋不準確之處,懇請指正。

JVM 有兩類載入器:啟動類載入器與使用者自定義載入器。類載入子系統透過類的全限定名(套件名+類名),網路載入還包含 URL,將 Class 載入執行時資料區。對每個被載入的類型,JVM 會建立一個 java.lang.Class 實例代表該類型,該實例置於堆區,而載入的類型資訊則位於方法區,與其他物件一致。
類載入子系統在載入類型前,除定位與導入對應的二進位 class 檔案外,還要驗證導入類的正確性、為類變數分配並初始化記憶體、解析符號引用為直接引用,嚴格按以下順序進行:
對每個被類載入子系統載入的類型,虛擬機會保存下列資料到方法區:
java.lang.Object 無超類)除基本類型資訊外,還保存:
一個指向 ClassLoader 的引用
// 返回剛才保存的 ClassLoader 引用
String.class.getClassLoader();
一個指向 Class 類的引用
// 返回剛才保存的 Class 類的引用
String.class;
注意:方法區同樣可被垃圾回收器回收。
Java 程式執行時建立的所有類實例或陣列都放在同一個堆中,每個 JVM 只有一個堆空間,所有執行緒共享一個堆(這就是多執行緒 Java 程式會產生物件存取同步問題的原因)。由於各 JVM 對規範有不同實作,我們可能不知每種 JVM 在堆中以何種形式表示物件實例,但可透過下圖一窺端倪:

對執行中的 Java 程式,每個執行緒都有自己的 PC(程式計數器)暫存器,在執行緒啟動時建立,大小為一個字長,用來保存下一行待執行代碼的位置。
每個執行緒都有一個 Java 堆疊,以堆疊幀為單位保存執行緒運行狀態。虛擬機對 Java 堆疊的操作有兩種:壓棧與出棧,皆以幀為單位。堆疊幀保存了傳入參數、區域變數、中間運算結果等資料,方法完成時被彈出並釋放。
兩個區域變數相加時堆疊幀的記憶體快照:

這是 Java 呼叫作業系統本地庫的地方,用來實現 JNI (Java Native Interface,Java 本地介面)。
JVM 的核心,控制裝入 Java 位元碼並解析。對執行中的 Java 程式,每個執行緒都是一個獨立的虛擬機執行引擎實例,從執行緒生命週期開始到結束,要麼在執行位元碼,要麼在執行本地方法。
連接了本地方法堆疊和作業系統庫。
註:文中所有提到 "Java 虛擬機" 的地方都是指 "JavaEE 和 JavaSE 平台的 Java 虛擬機規範"。