目錄
- 什麼是 JVM?
- JVM 記憶體架構概覽
- Heap vs Stack 核心差異
- 記憶體分配範例
- Heap 記憶體詳細結構
- Method Area(方法區 / Metaspace)
- 垃圾回收(Garbage Collection, GC)
- JVM 記憶體參數設定
- 常見記憶體問題
- 記憶體監控工具
- 效能調校建議
- 總結速查表
什麼是 JVM?
JVM(Java Virtual Machine,Java 虛擬機) 是執行 Java 程式的執行環境。
JVM 的作用
- 跨平台:一次編譯,到處執行(Write Once, Run Anywhere)
- 記憶體管理:自動垃圾回收(Garbage Collection, GC)
- 執行 Bytecode:將
.class檔案轉成機器碼執行
Java 程式執行流程
.java 原始碼 → javac 編譯 → .class Bytecode → JVM 執行 → 機器碼
JVM 記憶體架構概覽
Heap vs Stack 核心差異
Stack(堆疊)
用途:儲存方法執行期間的區域變數和方法呼叫資訊
特性:
- 執行緒專屬:每個執行緒有自己的 Stack
- 自動管理:方法結束後自動清除
- 速度快:存取速度快(LIFO,後進先出)
- 大小固定:預設 1MB(可調整)
- 儲存內容:
- 基本型別(int, long, double, boolean 等)的值
- 物件的參考(reference)
- 方法參數
- 區域變數
範例:
public void calculateSum() {
int a = 10; // a 存在 Stack
int b = 20; // b 存在 Stack
int sum = a + b; // sum 存在 Stack
}
// 方法結束,Stack 自動清空 a, b, sum
StackOverflowError:
- 發生原因:遞迴太深、Stack 空間不足
public void recursion() {
recursion(); // 無限遞迴,最終拋出 StackOverflowError
}
Heap(堆積)
用途:儲存所有物件實例和陣列
特性:
- 所有執行緒共享:全域共用的記憶體空間
- 需要 GC 管理:垃圾回收器負責清理
- 速度較慢:相對於 Stack
- 大小可調整:啟動時可設定
- 儲存內容:
- 所有
new出來的物件 - 物件的成員變數
- 陣列
- 所有
範例:
public void createObject() {
Person person = new Person("Alice");
// person(參考)在 Stack
// new Person()(物件實例)在 Heap
}
Heap vs Stack 快速對照
| 項目 | Stack | Heap |
|---|---|---|
| 用途 | 方法執行資訊、區域變數 | 物件實例、陣列 |
| 共享 | 執行緒專屬 | 所有執行緒共享 |
| 速度 | 快 | 較慢 |
| 管理 | 自動(方法結束即清除) | GC 管理 |
| 大小 | 小(預設 ~1MB) | 大(預設數百 MB 到數 GB) |
| 錯誤 | StackOverflowError | OutOfMemoryError |
| 儲存 | 基本型別值、參考 | 物件、陣列 |
記憶體分配範例
範例 1:基本型別 vs 物件
public void example() {
// Stack:值直接存在 Stack
int age = 30;
boolean isActive = true;
// Heap + Stack
String name = new String("Alice");
// name(參考)→ Stack
// "Alice" 物件 → Heap
Person person = new Person("Bob", 25);
// person(參考)→ Stack
// Person 物件 → Heap
}
記憶體示意圖:
Stack Heap
┌─────────────┐ ┌──────────────────┐
│ age: 30 │ │ String("Alice") │ ← name 指向
│ isActive: │ │ │
│ true │ │ Person { │ ← person 指向
│ name: 0x100 │ ────────>│ name: "Bob" │
│ person: │ │ age: 25 │
│ 0x200 │ ────────>│ } │
└─────────────┘ └──────────────────┘
範例 2:方法呼叫
public class Example {
public static void main(String[] args) {
int x = 10;
processData(x);
}
public static void processData(int value) {
String message = "Processing";
int result = value * 2;
}
}
Stack 變化:
1. main() 進入
Stack: [x=10]
2. processData() 被呼叫
Stack: [x=10] → [value=10, message(參考), result=20]
3. processData() 結束
Stack: [x=10] (processData 的資料被清除)
4. main() 結束
Stack: [] (清空)
Heap 記憶體詳細結構
Heap 分代(Generational)架構
Heap
├─ Young Generation(年輕代)- 新物件
│ ├─ Eden Space(伊甸園)- 新物件首次分配
│ └─ Survivor Space(倖存者空間)
│ ├─ S0(From)
│ └─ S1(To)
└─ Old Generation(老年代)- 長期存活的物件
Young Generation(年輕代)
Eden Space:
- 所有新物件最初分配的地方
- 大部分物件生命週期短,很快就被回收
Survivor Space (S0, S1):
- 從 Eden 存活下來的物件會移到這裡
- S0 和 S1 來回交替使用
- 經過多次 Minor GC 仍存活 → 晉升到 Old Generation
Minor GC(輕量 GC):
- 只清理 Young Generation
- 頻率高、速度快
- 影響較小
Old Generation(老年代)
存放物件:
- 長期存活的物件
- 大型物件(直接分配)
Major GC / Full GC(重量 GC):
- 清理 Old Generation
- 頻率低、速度慢
- 影響大(Stop The World)
物件生命週期
1. 新物件創建 → Eden Space
2. Eden 滿了 → Minor GC
├─ 存活的物件 → Survivor Space (S0)
└─ 死亡的物件 → 回收
3. 再次 Minor GC
├─ Eden + S0 存活的 → S1
└─ 死亡的 → 回收
4. 多次 GC 後仍存活(通常 15 次)
→ 晉升到 Old Generation
5. Old Generation 滿了 → Major GC / Full GC
Method Area(方法區 / Metaspace)
Java 7 之前:PermGen(永久代)
儲存內容:
- 類別資訊(Class metadata)
- 靜態變數
- 常量池(Constant Pool)
- 方法資訊
問題:
- 大小固定,容易
OutOfMemoryError: PermGen space - 調整困難
Java 8+ : Metaspace(元空間)
改進:
- 使用原生記憶體(Native Memory),不再受 Heap 限制
- 自動擴展,預設無上限
- 減少
OutOfMemoryError: PermGen space錯誤
儲存內容:
- 類別 metadata
- 方法資訊
- 常量池
設定參數:
-XX:MetaspaceSize=128m # 初始大小
-XX:MaxMetaspaceSize=256m # 最大大小
垃圾回收(Garbage Collection, GC)
什麼是 GC?
自動記憶體管理機制,回收不再使用的物件,釋放記憶體。
GC 如何判斷物件可以回收?
1. 引用計數(Reference Counting)- 不採用
每個物件維護引用數量,為 0 時回收。
問題:無法處理循環引用
ObjectA.ref = ObjectB;
ObjectB.ref = ObjectA;
// 兩者引用數都不為 0,但實際上都無法被訪問
2. 可達性分析(Reachability Analysis)- Java 採用
從 GC Roots 開始,標記所有可達物件,不可達的物件就是垃圾。
GC Roots 包括:
- Stack 中的局部變數
- 靜態變數
- 常量池中的引用
- JNI 引用
常見 GC 演算法
| 演算法 | 說明 | 優點 | 缺點 |
|---|---|---|---|
| Serial GC | 單執行緒 GC | 簡單 | 暫停時間長 |
| Parallel GC | 多執行緒 GC | 吞吐量高 | 暫停時間長 |
| CMS GC | 並發標記清除 | 暫停時間短 | 碎片化 |
| G1 GC | 分區回收 | 平衡吞吐量和延遲 | 複雜 |
| ZGC | 超低延遲 | 暫停 < 10ms | 需要大記憶體 |
G1 GC(Garbage First)
Java 9+ 預設 GC
特點:
- 將 Heap 分成多個小區域(Region)
- 優先回收垃圾最多的區域
- 可預測的暫停時間
適用情境:
- 大型 Heap(> 4GB)
- 需要可預測的低延遲
JVM 記憶體參數設定
常用參數
# Heap 大小設定
-Xms2g # 初始 Heap 大小(2GB)
-Xmx4g # 最大 Heap 大小(4GB)
# Young Generation 設定
-Xmn1g # Young Generation 大小(1GB)
-XX:NewRatio=2 # Old:Young 比例(2:1)
# Stack 大小設定
-Xss1m # 每個執行緒 Stack 大小(1MB)
# Metaspace 設定
-XX:MetaspaceSize=128m # 初始 Metaspace 大小
-XX:MaxMetaspaceSize=256m # 最大 Metaspace 大小
# GC 選擇
-XX:+UseG1GC # 使用 G1 GC
-XX:+UseSerialGC # 使用 Serial GC
-XX:+UseParallelGC # 使用 Parallel GC
-XX:+UseConcMarkSweepGC # 使用 CMS GC
# GC 日誌
-XX:+PrintGCDetails # 列印 GC 詳細資訊
-XX:+PrintGCDateStamps # GC 時間戳記
-Xloggc:/path/to/gc.log # GC 日誌檔案位置
# OutOfMemoryError 時產生 Heap Dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dumps
範例:啟動應用程式
java -Xms2g -Xmx4g -Xss1m \
-XX:+UseG1GC \
-XX:+PrintGCDetails \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/heapdump.hprof \
-jar myapp.jar
常見記憶體問題
1. OutOfMemoryError: Java heap space
原因:Heap 記憶體不足
解決方式:
# 增加 Heap 大小
-Xmx4g
# 或檢查記憶體洩漏(Memory Leak)
如何檢查:
# 使用 jmap 產生 Heap Dump
jmap -dump:live,format=b,file=heap.hprof <pid>
# 使用 MAT(Eclipse Memory Analyzer)分析
2. OutOfMemoryError: PermGen space(Java 7)
原因:PermGen 空間不足(類別太多)
解決方式:
# Java 7
-XX:PermSize=128m
-XX:MaxPermSize=256m
# Java 8+ 改用 Metaspace
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m
3. StackOverflowError
原因:Stack 空間不足(通常是遞迴太深)
解決方式:
# 增加 Stack 大小(治標)
-Xss2m
# 修正程式碼(治本)
# - 避免無限遞迴
# - 改用迴圈
# - 使用尾遞迴優化
4. 記憶體洩漏(Memory Leak)
常見原因:
- 靜態集合持有物件參考
- 未關閉的資源(檔案、連線)
- 監聽器未移除
- ThreadLocal 未清理
範例(記憶體洩漏):
public class LeakExample {
private static List<byte[]> list = new ArrayList<>();
public void addData() {
// 靜態 list 不斷增長,物件無法被 GC
list.add(new byte[1024 * 1024]); // 1MB
}
}
修正:
public class FixedExample {
private List<byte[]> list = new ArrayList<>();
public void addData() {
list.add(new byte[1024 * 1024]);
}
public void cleanup() {
list.clear(); // 清除參考
}
}
記憶體監控工具
1. jps - 查看 Java 進程
# 列出所有 Java 進程
jps -l
# 輸出範例:
# 12345 com.example.MyApplication
# 12346 org.apache.tomcat.Bootstrap
2. jstat - JVM 統計資訊
# 查看 GC 統計(每 1000ms 更新一次)
jstat -gc <pid> 1000
# 查看 Heap 使用情況
jstat -gccapacity <pid>
# 查看 GC 原因
jstat -gccause <pid>
輸出範例:
S0C S1C S0U S1U EC EU OC OU MC MU
10240 10240 0 8192 81920 40960 163840 81920 51200 48128
說明:
- S0C/S1C:Survivor 0/1 容量
- EC:Eden 容量
- EU:Eden 使用量
- OC:Old Generation 容量
- OU:Old Generation 使用量
- MC:Metaspace 容量
- MU:Metaspace 使用量
3. jmap - Heap Dump
# 產生 Heap Dump
jmap -dump:live,format=b,file=heap.hprof <pid>
# 查看 Heap 使用摘要
jmap -heap <pid>
# 查看物件統計
jmap -histo <pid>
4. jstack - 執行緒堆疊
# 產生執行緒堆疊快照
jstack <pid> > thread_dump.txt
# 偵測死鎖
jstack -l <pid>
5. VisualVM - 圖形化監控
# 啟動 VisualVM(JDK 自帶)
jvisualvm
功能:
- 即時監控 CPU、記憶體、執行緒
- Heap Dump 分析
- 執行緒分析
- Profiling
6. MAT (Eclipse Memory Analyzer Tool)
用途:分析 Heap Dump,找出記憶體洩漏
功能:
- Leak Suspects(洩漏懷疑)報告
- Dominator Tree(支配樹)
- OQL(物件查詢語言)
效能調校建議
1. 合理設定 Heap 大小
# 原則:-Xms 和 -Xmx 設為相同,避免 Heap 動態調整
-Xms4g -Xmx4g
# 不要設太小(頻繁 GC)
-Xmx512m # ❌ 太小
# 不要設太大(GC 暫停時間長)
-Xmx64g # ⚠️ 考慮使用 ZGC
2. 選擇合適的 GC
| 應用場景 | 推薦 GC |
|---|---|
| 小型應用(< 1GB Heap) | Serial GC |
| 批次處理(追求吞吐量) | Parallel GC |
| 低延遲應用(< 4GB Heap) | CMS GC |
| 一般應用(> 4GB Heap) | G1 GC(預設) |
| 超低延遲(> 8GB Heap) | ZGC / Shenandoah |
3. 避免記憶體洩漏
// ✅ 使用 try-with-resources 自動關閉資源
try (FileInputStream fis = new FileInputStream("file.txt")) {
// 使用 fis
} // 自動關閉
// ✅ 移除監聽器
button.removeActionListener(listener);
// ✅ 清理 ThreadLocal
threadLocal.remove();
// ✅ 使用 WeakReference 避免強引用
WeakReference<MyObject> weakRef = new WeakReference<>(obj);
4. 物件池(Object Pooling)
對於頻繁創建和銷毀的物件:
// 使用 Apache Commons Pool
GenericObjectPool<MyObject> pool = new GenericObjectPool<>(factory);
MyObject obj = pool.borrowObject();
try {
// 使用 obj
} finally {
pool.returnObject(obj);
}
快速檢查清單
記憶體問題排查步驟
-
確認問題類型
# 查看錯誤訊息 OutOfMemoryError: Java heap space → Heap 不足 StackOverflowError → Stack 不足(遞迴問題) -
檢查記憶體使用
jstat -gc <pid> 1000 -
產生 Heap Dump
jmap -dump:live,format=b,file=heap.hprof <pid> -
分析 Heap Dump
- 使用 MAT 或 VisualVM
- 查找大物件和記憶體洩漏
-
調整 JVM 參數
-Xms4g -Xmx4g -XX:+UseG1GC
總結速查表
記憶體區域對照
| 區域 | 儲存內容 | 共享 | 管理 | 大小 |
|---|---|---|---|---|
| Heap | 物件、陣列 | 共享 | GC | 大 |
| Stack | 區域變數、參考 | 執行緒專屬 | 自動 | 小 |
| Metaspace | 類別資訊 | 共享 | 自動擴展 | 中 |
關鍵 JVM 參數
-Xms2g -Xmx4g # Heap 大小
-Xss1m # Stack 大小
-XX:+UseG1GC # 使用 G1 GC
-XX:MetaspaceSize=128m # Metaspace 大小
-XX:+HeapDumpOnOutOfMemoryError # OOM 時產生 Heap Dump
常用監控命令
jps -l # 查看 Java 進程
jstat -gc <pid> 1000 # 監控 GC
jmap -heap <pid> # 查看 Heap 資訊
jstack <pid> # 查看執行緒堆疊
jmap -dump:live,format=b,file=heap.hprof <pid> # Heap Dump
記憶體最佳實踐
- ✅ 合理設定 Heap:
-Xms=-Xmx - ✅ 選擇合適 GC:一般用 G1 GC
- ✅ 監控記憶體:定期檢查 GC 日誌
- ✅ 避免洩漏:關閉資源、清理參考
- ✅ 分析 Dump:使用 MAT 找出問題
實用範例
完整的 JVM 啟動配置
#!/bin/bash
JAVA_OPTS="
-server
-Xms4g
-Xmx4g
-Xss1m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/myapp/heapdump.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/myapp/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=20M
"
java $JAVA_OPTS -jar myapp.jar
建立日期:2024-11-05 最後更新:2025-11-18