HotSpot JVM 將堆劃分為:新生代、老年代、永久代/元空間。不同區域滿載會拋出不同的 OOM 異常。
java.lang.OutOfMemoryError: Java heap space
-Xmx 過小)-XX:PretenureSizeThreshold)-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof-Xms2g -Xmx2g (建議 Xms=Xmx 避免震蕩)# Java 7 永久代
java.lang.OutOfMemoryError: PermGen space
# Java 8+ 元空間
java.lang.OutOfMemoryError: Metaspace
String.intern() 濫用)-XX:PermSize=256m -XX:MaxPermSize=512m-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512mString.intern() 大量使用java.lang.OutOfMemoryError: unable to create new native thread
執行緒建立需要本機記憶體 (非堆),公式:
可用本機記憶體 = 進程最大記憶體 - (-Xmx) - (-XX:MaxMetaspaceSize) - 程式計數器 - 其他
-Xmx 設定過大,擠佔本機記憶體-Xss (執行緒棧大小) 設定過大 (預設 1MB)Executors.newFixedThreadPool() 限制並發數-Xss256k (需測試棧溢出風險)ulimit -u 65535java.lang.StackOverflowError
-Xss2m (治標不治本)java.lang.OutOfMemoryError: Direct buffer memory
ByteBuffer.allocateDirect() 大量分配未釋放-XX:MaxDirectMemorySize 限制 (預設等於 -Xmx)-XX:MaxDirectMemorySize=2gCleaner.clean() 或依賴 GC (較慢)-Dio.netty.maxDirectMemory)| 工具 | 用途 | 適用階段 |
|---|---|---|
| jstat -gcutil | GC 頻率、各區使用率 | 線上快速觀測 |
| jmap -histo:live | 存活物件統計 | 定位大物件類型 |
| jmap -dump | 生成堆快照 | 離線深度分析 |
| MAT (Eclipse Memory Analyzer) | 支配樹、洩漏報告、OQL 查詢 | 堆傾印深度分析 (首選) |
| JVisualVM / JMC | 視覺化監控、取樣分析 | 開發測試階段 |
| Arthas / Greys | 線上診斷、類載入統計、執行緒狀態 | 生產環境零侵入診斷 |
| GC 日誌分析 (GCeasy, gceasy.io) | GC 停頓、吞吐量、擴容/縮容趨勢 | GC 調優 |
.hprof → Leak Suspects Report 自動分析洩漏嫌疑// 錯誤
static List<User> userCache = new ArrayList<>();
userCache.add(user); // 永不清理
// 修復
static Map<String, User> userCache = new ConcurrentHashMap<>();
userCache.put(key, user);
// 定時清理或用弱引用
static Map<String, WeakReference<User>> weakCache = new ConcurrentHashMap<>();
// 錯誤
ThreadLocal<Connection> tl = new ThreadLocal<>();
tl.set(conn); // 執行緒池複用時不會自動清理
// 修復
try {
tl.set(conn);
// 業務邏輯
} finally {
tl.remove(); // 必須在 finally 中清理
}
// 錯誤:非靜態內部類隱含持有外部類 this 參考
class Outer {
byte[] bigData = new byte[10*1024*1024];
class Inner {} // 持有 Outer 參考
Inner createInner() { return new Inner(); }
}
// 修復:靜態內部類或傳遞所需資料而非 this
static class Inner {}
// 修復:try-with-resources (Java 7+)
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// 使用資源
} // 自動關閉
# 基礎堆設定 (建議 Xms=Xmx)
-Xms4g -Xmx4g
# 新生代/老年代比例
-Xmn1g # 新生代 1GB
-XX:NewRatio=2 # 老年代:新生代 = 2:1
# 元空間
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
# 直接記憶體
-XX:MaxDirectMemorySize=1g
# 執行緒棧
-Xss512k
# GC 選擇 (JDK 11+)
-XX:+UseG1GC # G1 (大堆、低延遲)
-XX:+UseZGC # ZGC (超大堆、亞毫秒延遲)
-XX:+UseShenandoahGC # Shenandoah
# GC 日誌 (生產必開)
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags:filecount=10,filesize=100m
# OOM 傾印
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/dumps/heap_%p_%t.hprof
# 類卸載 (解決 PermGen/Metaspace 洩漏)
-XX:+CMSClassUnloadingEnabled (CMS)
-XX:+ClassUnloadingWithConcurrentMark (G1)
核心原則:參數調優是治標,代碼修復是治本。大多數 OOM 根源於程式碼的記憶體洩漏或不合理的資料處理邏輯,而非單純記憶體不足。