JVM 運行時資料區域
Java 虛擬機在執行程式時會將記憶體劃分為若干區域,每個區域有特定用途與生命週期:
| 區域 | 線程共享性 | 主要內容 | OOM 風險 |
| 程式計數器 | 線程私有 | 當前執行的位元碼指令地址 | 無 |
| Java 虛擬機棧 | 線程私有 | 棧幀(局部變數表、操作數棧、動態連結、方法返回地址) | StackOverflowError / OOM |
| 本地方法棧 | 線程私有 | Native 方法調用狀態 | StackOverflowError / OOM |
| 堆 | 線程共享 | 物件實例、陣列、字串常量池(JDK 7+) | OOM |
| 方法區 | 線程共享 | 類元資料、常量、靜態變數、即時編譯器代碼緩存 | OOM |
| 直接記憶體 | 不受 JVM 直接管理 | NIO DirectBuffer、Native 分配 | OOM |
重點:堆記憶體分代結構
┌─────────────────────────────────────┐
│ Heap │
├──────────────┬──────────────────────┤
│ Young Gen │ Old Gen │
│ (新生代) │ (老年代) │
│ ┌──────┐ ┌──┐│ │
│ │ Eden │ │S0││ 長期存活物件 │
│ └──────┘ │ ││ (大物件、長壽命) │
│ │S1││ │
│ └──┘│ │
└──────────────┴──────────────────────┘
- Eden 區:新物件誕生地,大多數物件朝生夕死。
- Survivor 區 (S0/S1):經過一次 Minor GC 仍存活的物件進入,雙區輪換複製。
- 老年代:經過多次 GC 仍存活(預設年齡 ≥ 15)或大物件直接進入。
方法區演進
- JDK 7:永久代,固定大小,易 OOM。
- JDK 8:元空間,使用本地記憶體,預設無上限(受物理記憶體限制)。
- 類元資料、常量池、靜態變數移至元空間;字串常量池、靜態變數實例移至堆。
垃圾回收核心演算法
對象存活判定
- 引用計數法:簡單但無法解決循環引用,JVM 未採用。
- 可達性分析法(標準):
- GC Roots:棧幀局部變數、方法區靜態屬性/常量、JNI 引用、同步鎖持有物件等。
- 從 GC Roots 向下搜索,不可達物件標記為可回收。
- 需經過兩次標記(第一次標記、第二次篩選 finalize())才真正回收。
四大基礎演算法
| 演算法 | 核心思想 | 優點 | 缺點 | 適用區域 |
| 標記-清除 | 標記存活,清除其餘 | 實現簡單 | 記憶體碎片化、效率低 | 老年代 |
| 複製 | 劃分兩塊,存活者複製 | 無碎片、分配快 | 記憶體利用率 50% | 新生代 |
| 標記-整理 | 標記後向一端移動 | 無碎片、利用率高 | 移動開銷大 | 老年代 |
| 分代收集 | 依壽命分區,異策略 | 綜合優勢、高效 | 實現複雜 | 全堆 |
新生代:複製演算法優化
- 實際劃分:Eden : S0 : S1 = 8 : 1 : 1(可調
-XX:SurvivorRatio)。
- 每次使用 Eden + 一個 Survivor,GC 時存活者複製到另一 Survivor,清空 Eden 和用過的 Survivor。
- 大物件(
-XX:PretenureSizeThreshold)或長壽命物件(-XX:MaxTenuringThreshold)直接進老年代。
老年代:標記-整理/標記-清除
- Parallel Old:標記-整理,多線程並行。
- CMS:標記-清除,併發低停頓,但產生碎片。
- G1:區域化分代,混合收集,可預測停頓。
- ZGC/Shenandoah:併發整理,超低停頓(< 10ms)。
常用 GC 收集器對比
| 收集器 | 模式 | 新生代/老年代 | 演算法 | 特點 | 適用場景 |
| Serial | 串行 | 新/老 | 複製/標記-整理 | 單線程、暫停所有應用線程 | Client 模式、小應用 |
| Parallel Scavenge | 並行 | 新生代 | 複製 | 高吞吐量、自適應調優 | 批次處理、科學計算 |
| Parallel Old | 並行 | 老年代 | 標記-整理 | 多線程、高吞吐 | 搭配 Parallel Scavenge |
| CMS | 併發 | 老年代 | 標記-清除 | 低停頓、併發收集、碎片化 | Web 服務、響應敏感 |
| G1 | 混合 | 全堆 | 標記-整理+複製 | 區域化、可預測停頓、大堆友好 | 大記憶體、多核、JDK 9+ 預設 |
| ZGC | 併發 | 全堆 | 併發整理 | 亞毫秒停頓、TB 級堆 | 超大堆、極低延遲需求 |
開發者優化指南
減少 GC 壓力
- 避免顯式
System.gc():觸發 Full GC,影響效能。
- 減少臨時物件:複用物件、使用物件池、基本型別替代包裝類。
- 字串拼接用
StringBuilder:避免大量 String 臨時物件。
- 及時釋放引用:作用域結束賦
null、集合用完 clear()。
- 謹慎使用靜態集合:生命週期長,易造成記憶體洩漏。
JVM 參數調優關鍵
-Xms512m -Xmx512m # 堆固定大小,避免震盪
-Xmn256m # 新生代大小
-XX:SurvivorRatio=8 # Eden:Survivor 比例
-XX:MaxTenuringThreshold=15 # 晉升老年代年齡
-XX:+UseG1GC # 啟用 G1
-XX:MaxGCPauseMillis=200 # 目標停頓時間
-XX:+HeapDumpOnOutOfMemoryError # OOM 時導出堆快照
-XX:HeapDumpPath=/logs/heap.hprof
排查工具鏈
- jstat / jmap / jhat:命令列監控與堆分析。
- VisualVM / JConsole:圖形化監控、堆 Dump 分析。
- Arthas / Async Profiler:線上診斷、火焰圖。
- MAT (Memory Analyzer Tool):深度堆分析、洩漏追蹤。
總結
理解 JVM 記憶體模型與 GC 機制,是解決 OOM、長停頓、記憶體洩漏等生產疑難雜症的前提。掌握分代假設、收集器特性、調優參數與工具鏈,能讓 Java 應用在高併發、大數據、低延遲場景下穩定高效運行。
- 上一篇文章: Java 與 Ceylon 物件建構驗證對比:從防禦性編程到類型安全工廠模式
- 下一篇文章: Java 多態機制深度解析與經典面試題詳解
Copyright ©
程式碼之家 All Rights Reserved