1.1 什麼是Java的記憶體溢位?
在Java程式執行的過程中,經常會碰到以下錯誤:java.lang.OutOfMemoryError。
通俗講,記憶體溢位是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現OutOfMemoryError。
1.2 產生原因?
簡單來講為以下兩點:
1. JVM記憶體過小
2. 產生過多的,沒有被回收的垃圾
以下討論主要基於JVM上不同記憶體區域的討論
1.3 Java堆溢位
Java堆是存放物件例項的地方,當我們不斷申請建立物件,並且保證這些物件始終可以從GC Roots
可達,總容量就會觸及最大堆的容量限制而丟擲記憶體溢位異常
例如以下程式碼,將虛擬機器的初始大小設為 20M ,並且不可變(將堆的最小值 -Xms 和 堆的最大值 -Xmx 設定為一樣可以避免Java堆自動擴充套件!)
public class OOM {
/*
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
static class OOMObject{
}
public static void main(String[] args){
List<OOMObject> list = new ArrayList<>();
while(true){
list.add(new OOMObject());
}
}
}
解決方法
常規處理方法是透過記憶體映像分析工具對 Dump 出來的堆轉儲快照進行分析。
1.首先分析是記憶體洩漏還是記憶體溢位
2.如果是記憶體洩漏透過工具檢視洩露物件到 GC Roots 的引用鏈,分析垃圾收集器無法回收他們的原因,進而定位到出現問題的程式碼
3.如果不是記憶體洩漏,即物件都應該必須活著,就應該對比 JVM堆記憶體 和機器記憶體相比是否還有向上調整的空間;或者從程式碼上檢查某些物件是否生命週期過長,持有狀態時間過長,儲存結構設計不合理等,儘量減少程式執行期間的記憶體消耗
1.4 虛擬機器棧和本地方法棧溢位
1. 如果執行緒請求棧的深度大於虛擬機器所允許的最大深度,將丟擲 StackOverflowError
異常
2. 如果虛擬機器的棧記憶體允許動態的擴充套件,當擴充套件棧的容量無法申請到足夠的記憶體的時候,將丟擲’OutOfMemoryError’異常
解決方法
1.出現
StackOverflowError
異常時,會有明確錯誤堆疊可供分析,比較容易定位問題所在,例如遞迴沒有終止條件。棧深度大多數情況下到達1000-2000是沒有問題的,對於正常方法的呼叫,這個深度完全是夠用的。
2.但如果是因為建立過多執行緒導致記憶體溢位,在不能減少執行緒的數量的情況下,只能透過減少最大堆的容量或者減少棧的容量來獲取更多的執行緒!
1.5 方法區和執行時常量池溢位
由於在JDK 8 以後,永久代退出了歷史舞臺,元空間作為其替代者登場,即元空間使用的是直接記憶體,受限於本機實體記憶體的大小,不再容易丟擲方法區的記憶體溢位。而在JDK 8 之前,方法區的實現永久代是會因為載入了大量的類(比如CG Lib位元組碼技術)而丟擲方法區記憶體溢位的,或者因為執行時常量池(JDK 6 還是屬於方法區的一部分,丟擲的是方法區的記憶體溢位;而JDK7之後便移到Java堆上,丟擲的是java的堆記憶體溢位)而丟擲對應區域的記憶體溢位!
1.6 本機直接記憶體溢位
直接記憶體可以透過 -XX:MaxDirectMemorySize
引數來指定,若不指定則預設與堆的最大值保持一致。
由直接記憶體導致的記憶體溢位,一個明顯的特徵就是在dump下的檔案不會看到有明顯的異常情況,或者該檔案很小,而程式又直接或者間接的使用了 Direct Memory ,就應該去考慮是否是本機直接記憶體溢位
Memory Leak,是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆積後果很嚴重,無論多少記憶體,遲早會被佔光。
在Java中,記憶體洩漏就是存在一些被分配的物件,這些物件有下面兩個特點:
- 首先,這些物件是可達的,即在有向圖中,存在通路可以與其相連;
- 其次,這些物件是無用的,即程式以後不會再使用這些物件。
如果物件滿足這兩個條件,這些物件就可以判定為Java中的記憶體洩漏,這些物件不會被GC所回收,然而它卻佔用記憶體。
記憶體洩露會最終會導致記憶體溢位。
相同點:都會導致應用程式執行出現問題,效能下降或掛起。
不同點:
1. 記憶體洩露是導致記憶體溢位的原因之一,記憶體洩露積累起來將導致記憶體溢位。
1. 記憶體洩露可以透過完善程式碼來避免,記憶體溢位可以透過調整配置來減少發生頻率,但無法徹底避免。
本作品採用《CC 協議》,轉載必須註明作者和本文連結