一、Java的記憶體模型
《Java虛拟機規範》規定虛拟機包括以下幾個運作時資料區:
- 方法區 - Method Area
- 虛拟機棧 - VM Stack
- 本地方法棧 - Native Method Stack
- 堆 - Heap
- 程式計數器 - Program Counter Register
在不同的
JVM
運作時實作中可能存在差異
常見的
JVM
運作時有:
- OracleJDK
- OpenJDK
- IBM J9
- JRocket
- Alibaba Dragonwell
- Tencent Kona
- Graalvm
比如
OracleJDK8
的方法區區叫元空間,具體不再展開...
不考慮差異性,本文主旨是線上
OracleJDK8
說明記憶體溢出出現的場景,排查方式和解決方案
二、記憶體溢出類型
- StackOverflowError
- OutOfMemoryError
StackOverflowError
當線程棧深度超過設定的最大深度,則由虛拟機抛出
OutOfMemoryError
當無法通過JVM申請到記憶體時,則由虛拟機抛出
除了程式計數器外,棧、堆、非堆(直接記憶體)都可以抛出
OOM
常見的異常錯誤及通用解:
- java.lang.StackOverflowError : Thread Stack space
原因:當線程棧深度超過設定的最大深度,則由虛拟機抛出
解決:
1)使用循環替換遞歸
2)使用排程器MapReduce思想來實作分之歸并
- java.lang.OutOfMemoryError: Java heap space
原因:目前線程無法從JVM申請堆記憶體空間
解決:
1)具體問題具體分析,當定位到是大對象時,需要優化代碼為小對象
2)當定位到是堆記憶體空間太小,修改VM配置。這裡需評估并發量,經過壓測和優化後得出
3)優化代碼,減少處理耗時,如果是高耗時操作,可以在對象不使用後,去掉局部變量對大對象的引用
4)當定位到是記憶體洩露,則需要修改洩露代碼(通常在引用第三方包出現)
- java.lang.OutOfMemoryError: Requested array size exceeds VM limit
原因:申請數組大小超過JVM允許值,不同JVM版本存在差異
解決:
1) 修改申請數組小大
- java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
原因:當 GC 為釋放很小空間占用大量時間時抛出,通常會伴随着CPU100%異常,線上系統嚴重卡頓
解決:跟
Java heap space
處理方法一樣
- java.lang.OutOfMemoryError: Metaspace
原因:目前線程無法從JVM申請元空間記憶體,JDK8之後原永久代中的類中繼資料遷移到元空間(堆 -> 直接記憶體),常量池繼續在堆中
解決:
1)當定位到是元空間設定太小,修改配置
2)當定位到是記憶體洩露,則修改代碼(通常在引用第三方包出現)
- java.lang.OutOfMemoryError: Direct buffer memory
原因:一般是工具為了做零拷貝,直接将資料存儲在直接記憶體中,減少使用者态和核心态記憶體拷貝,當未做記憶體回收時,空間不釋放,抛出異常
解決:
1)定位到直接記憶體設定太小,修改配置
- VisualVM
- Eclipse MAT(Memory Analyzer Tool)
- Alibab Arthas