JVM 是 Java 程序運行的核心容器,掌握其內存結構、垃圾回收機制、類加載過程、調優參數 是高級 Java 工程師的必修課。本文系統梳理 JVM 核心知識點,附圖解與實戰配置。

| 區域 | 線程私有/共享 | 核心功能 | OOM 風險 |
|---|---|---|---|
| 程序計數器 (PC Register) | 私有 | 當前線程執行字節碼行號指示器 | 無 |
| 虛擬機棧 (JVM Stack) | 私有 | 存放棧幀:局部變量表、操作數棧、動態鏈接、方法出口 | StackOverflowError (遞歸過深) / OOM (線程過多) |
| 本地方法棧 (Native Method Stack) | 私有 | 服務 Native 方法調用 | 同 JVM Stack |
| 堆 | 共享 | 對象實例分配區,GC 主戰場 | java.lang.OutOfMemoryError: Java heap space |
| 方法區 (Metaspace, JDK 8+) | 共享 | 類元數據、常量、靜態變量、JIT 編譯代碼 | OutOfMemoryError: Metaspace |
| 直接內存 | 共享 | NIO DirectByteBuffer 分配,不受堆限制 | OutOfMemoryError: Direct buffer memory |

<init> 構造方法TLAB (Thread Local Allocation Buffer):每線程在 Eden 劃分專屬分配區,避免多線程 CAS 競爭,提升分配吞吐。
| 算法 | 原理 | 優點 | 缺點 | 適用區域 |
|---|---|---|---|---|
| 標記-清除 | 標記存活,清除未標記 | 實現簡單 | 產生內存碎片,效率低 | 老年代 (CMS 初期) |
| 標記-整理 | 標記後向一端移動存活對象 | 無碎片 | 需移動對象,更新引用,停頓長 | 老年代 (Parallel Old, G1 混合) |
| 復制 | 劃分兩塊,存活對象復制到另一塊 | 無碎片,分配快 | 浪費 50% 空間 | 新生代 (Serial, Parallel, G1 Young) |
| 分代收集 | 按對象壽命分代,不同代用不同算法 | 綜合上述優點 | 實現複雜 | 全堆 (所有商用 GC) |
GC Roots 包括:棧中引用、方法區靜態屬性/常量引用、本地方法棧 JNI 引用、同步鎖持有對象、類對象等。不可達對象標記為可回收。
| 收集器 | 模式 | 適用區域 | 算法 | 特點 | 啟用參數 |
|---|---|---|---|---|---|
| Serial | 單線程 | 新生代/老年代 | 復制/標記-整理 | 簡單高效,Client 模式默認 | -XX:+UseSerialGC |
| ParNew | 多線程 | 新生代 | 復制 | Serial 多線程版,配合 CMS | -XX:+UseParNewGC |
| Parallel Scavenge | 多線程 | 新生代 | 復制 | 關注吞吐量,自適應調節 | -XX:+UseParallelGC |
| Parallel Old | 多線程 | 老年代 | 標記-整理 | Parallel Scavenge 老年代版 | -XX:+UseParallelOldGC |
| CMS | 並發 | 老年代 | 標記-清除 | 低停頓,碎片整理需 Full GC | -XX:+UseConcMarkSweepGC |
| G1 | 並發+分區 | 全堆 | 標記-整理+復制 | 大堆、可預測停頓、Region 劃分 | -XX:+UseG1GC (JDK 9+ 默認) |
| ZGC | 並發 | 全堆 | 着色指針+讀屏障 | 亞毫秒停頓,TB 級堆 | -XX:+UseZGC (JDK 11+) |
| Shenandoah | 並發 | 全堆 | 轉發指針+寫屏障 | 低停頓,與 ZGC 類似 | -XX:+UseShenandoahGC |
缺陷:碎片化、浮動垃圾、CPU 敏感。JDK 9 標記棄用,JDK 14 移除。
# 堆基礎 -Xms4g -Xmx4g # 初始/最大堆,建議設為相同值避免震蕩 -Xmn1.5g # 新生代大小,建議堆的 3/8 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m # 元空間 # 線程棧 -Xss1m # 每線程棧大小,默認 1M,輕量服務可降至 512k/256k # 分代比例 -XX:NewRatio=2 # 老年代:新生代 = 2:1 -XX:SurvivorRatio=8 # Eden:Survivor = 8:1 -XX:MaxTenuringThreshold=15 # 晉升老年代年齡閾值,默認 15 (CMS/G1 動態) # GC 選擇 (三選一) -XX:+UseParallelGC -XX:+UseParallelOldGC # 吞吐量優先,批處理、計算型 -XX:+UseG1GC # 綜合場景,大堆(>6G)、低延遲要求 -XX:+UseZGC # 超大堆(>16G)、極致低延遲 (JDK 11+) # G1 專屬 -XX:MaxGCPauseMillis=200 # 目標停頓時間 -XX:G1HeapRegionSize=16m # Region 大小 (1-32M, 2 的冪) -XX:InitiatingHeapOccupancyPercent=45 # 觸發並發標記堆佔比 # GC 日誌 (JDK 8/9+ 統一) -Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags:filecount=10,filesize=100m # OOM 保留現場 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps/heap.hprof -XX:OnOutOfMemoryError="bash /scripts/alert.sh %p"
主內存 vs 工作內存。volatile 保證讀寫直接作用主內存,禁止指令重排,實現可見性(不保證原子性)。
happens-before 規則:程序順序、鎖規則、volatile 規則、線程啟動/終止、傳遞性。synchronized、Lock 保證原子性+可見性+有序性。
| 異常 | 原因 | 排查工具/手段 |
|---|---|---|
| Java heap space | 堆不足/洩漏/對象過大 | MAT 分析 heap dump、GC 日誌、Arthas dashboard |
| Metaspace | 類加載過多/動態代理/反射 | jcmd PID VM.metaspace、調大 MaxMetaspaceSize |
| StackOverflowError | 遞歸過深/循環調用 | 增大 -Xss、優化算法、尾遞歸 |
| Unable to create native thread | 線程數超限/棧過大 | ulimit -u、降低 -Xss、線程池限流 |
| Direct buffer memory | NIO 直接內存洩漏 | -XX:MaxDirectMemorySize、Cleaner 清理、檢查 Netty 泄漏 |
dashboard、trace、watch、heapdumpfinalize() 已棄用:不可靠、性能差,替代 Cleaner/try-with-resources。System.gc():觸發 Full GC,STW 長,破壞分代假說節奏。JVM 調優無銀彈,核心在於理解內存模型、選對收集器、設定合理參數、持續觀測指標。建議從 G1 (JDK 11+ 默認) 起步,根據業務延遲/吞吐目標微調 MaxGCPauseMillis、InitiatingHeapOccupancyPercent,配合 Xlog:gc* 日誌與 Arthas 實時診斷,逐步構建穩定高效的運行時環境。