JVM 內存區域劃分
JVM 管理兩大類內存:
- 堆:存放對象實例,開發者主戰場,GC 主要作用區域。
- 非堆:方法區、永久代/元空間,存放類元數據、常量、靜態變量、JIT 編譯代碼等,GC 行為不同。
類型一:java.lang.OutOfMemoryError: PermGen space(Java 7 及之前)
成因
- 加載大量類(第三方 JAR 多、動態代理、JSP 編譯產生大量類)。
MaxPermSize 設置過小(默認僅 64MB/85MB)。
- 熱部署未清理舊類加載器,導致類元數據累積(Tomcat 典型問題)。
解決方案
# Java 7 典型啟動參數
-Xms800m -Xmx800m
-XX:PermSize=128m
-XX:MaxPermSize=256m
-XX:MaxNewSize=256m
關鍵點:將 PermSize 與 MaxPermSize 設大且相等,避免擴縮容抖動;熱部署場景需關注類加載器洩漏。
類型二:java.lang.OutOfMemoryError: Java heap space
成因
- 對象創建過多、生命週期過長、大對象直接進老年代。
- 堆內存設置過小(
-Xmx 默認物理內存 1/4)。
- 內存洩漏:集合持有失效引用、靜態集合累積、未關閉資源、ThreadLocal 未清理等。
堆內存動態調節機制
- 空閒 < 40% → 擴容至
-Xmx
- 空閒 > 70% → 縮容至
-Xms
- 生產環境建議
-Xms = -Xmx,避免頻繁 GC 觸發的堆大小調整開銷。
32 位 vs 64 位限制
- 32 位 JVM 最大堆約 1.5GB~3GB(受 OS 限制)。
- 64 位 JVM 無此限制,但指針膨脹會增加內存佔用(可用
-XX:+UseCompressedOops 優化)。
GC 的角色與誤區
- GC 觸發條件:應用線程空閒、堆內存不足。
System.gc() 僅是建議,不保證立即執行,不能依賴它解決 OOM。
- 頻繁 Full GC 是 OOM 前兆,需通過代碼優化減少垃圾對象產生,而非指望 GC 救急。
排查工具鏈
- jstat -gcutil:觀察 GC 頻率、各代佔用率。
- jmap -histo:live / jmap -dump:導出堆快照。
- MAT (Memory Analyzer Tool) / JVisualVM:分析堆轉儲,定位洩漏路徑(GC Roots 追溯)。
- Arthas / Async Profiler:線上診斷類加載、對象分配熱點。
經典調優清單
| 場景 | 建議參數/動作 |
| 大量第三方庫/動態類 | 增大 Metaspace/PermGen;排查類加載器洩漏 |
| 大對象頻繁分配 | -XX:PretenureSizeThreshold 讓大對象直入老年代;增大老年代 |
| 長生命周期緩存 | 使用 WeakReference/SoftReference;限制緩存大小(如 Caffeine、Ehcache) |
| 線程池/連接池洩漏 | 檢查 shutdown、remove 邏輯;ThreadLocal 用完 remove() |
總結
PermGen/MetaSpace OOM 本質是類元數據區不足,Heap OOM 本質是對象實例區不足或洩漏。解決核心在於:合理規劃內存分區大小、消除代碼層面的內存洩漏、建立監控預警機制。參數調優是治標,代碼治本。