1. 程式人生 > >面試JVM(六)OOM

面試JVM(六)OOM

OutOfMemoryError異常:

目錄

1:java堆溢位

2:虛擬機器棧和本地方法棧溢位

3:方法區和執行時常量池溢位

4:本地直接記憶體溢位


1:java堆溢位

要同時滿足兩個條件:

  1. 堆大小固定,不可擴充套件
  2. 不斷建立物件,並保持物件存活不被回收

深入理解java虛擬機器原文:

Java堆用於儲存物件例項,只要不斷地建立物件,並且保證GC Roots到物件之間有可達路徑來避免垃圾回收機制清除這些物件,那麼在物件數量到達最大堆的容量限制後就會產生記憶體溢位異常。

下面程式碼限制Java堆的大小為20MB,不可擴充套件(將堆的最小值-Xms引數與最大值-Xmx引數設定為一樣即可避免堆自動擴充套件),通過引數-XX:+HeapDumpOnOutOfMemoryError可以讓虛擬機器在出現記憶體溢位異常時Dump出當前的記憶體堆轉儲快照以便事後進行分析。public class HeapOOM {
    static class OOMObject{}
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<HeapOOM.OOMObject>();
        while(true) {
            list.add(new OOMObject());
       }
    }   
}

Java堆記憶體的OOM異常是實際應用中常見的記憶體溢位異常情況。當出現Java堆記憶體溢位時,異常堆疊資訊“java.lang.OutOfMemoryError”會跟著進一步提示“Java heap space”。

        要解決這個區域的異常,一般的手段是先通過記憶體映像分析工具(如Eclipse Memory Analyzer)對Dump出來的堆轉儲快照進行分析,重點是確認記憶體中的物件是否是必要的,也就是要先分清除到底是出現了記憶體洩漏(Memory Leak)還是記憶體溢位(Memory Overflow)。下圖顯示了使用Eclipse Memory Analyzer開啟的堆轉儲快照檔案。

如果是記憶體洩漏,可進一步通過工具檢視洩漏物件到GC Roots的引用鏈。於是就能找到洩露物件是通過怎樣的路徑與GC Roots相關聯並導致垃圾收集器無法自動回收他們的。掌握了洩漏物件的型別資訊及GC Roots引用鏈的資訊,就可以比較準確的定位出洩漏程式碼的位置。

        如果不存在洩漏,換句話說,就是記憶體中的物件確實還必須存活著,那就應當檢查虛擬機器的堆引數(-Xmx與-Xms),與機器實體記憶體對比看是否還可以調大,從程式碼上檢查虛擬機器的堆引數(-Xmx與-Xms),與機器實體記憶體對比看是否還可以調大,從程式碼上檢查是否存在某些物件生命週期過長、持有狀態時間過長的情況,嘗試減少的程式執行期的記憶體消耗。

 

2:虛擬機器棧和本地方法棧溢位

一、在 Java 虛擬機器規範中,對虛擬機器棧這個區域規定了兩種異常狀況:

(1)如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError 異常; 
(2)如果虛擬機器棧可以動態擴充套件時無法申請到足夠的記憶體空間時會丟擲 OutOfMemoryError 異常。 

在單執行緒下,無論是棧幀太大還是虛擬機器棧容量太小,當記憶體無法分配時,虛擬機器都丟擲StackOverflowError 異常。

如果測試時不限於單執行緒,通過不斷地建立執行緒的方式倒是可以產生記憶體溢位異常。

為什嗎多執行緒的情況下就會產生OOM,這樣產生的記憶體溢位異常與棧空間是否足夠大並不存在任何聯絡,或者準確地說,在這種情況下,給每個執行緒的棧分配的記憶體越大,反而越容易產生記憶體溢位異常。

原因其實不難理解,作業系統分配給每個程序的記憶體是有限制的,譬如 32 位的 Windows 限制為 2GB。虛擬機器提供了引數來控制 Java 堆和方法區的這兩部分記憶體的最大值。剩餘的記憶體為 2GB(作業系統限制)減去 Xmx(最大堆容量),再減去 MaxPermSize(最大方法區容量),程式計數器消耗記憶體很小,可以忽略掉。如果虛擬機器程序本身耗費的記憶體不計算在內,剩下的記憶體就由虛擬機器棧和本地方法棧“瓜分”了。每個執行緒分配到的棧容量越大,可以建立的執行緒數量自然就越少,建立執行緒時就越容易把剩下的記憶體耗盡。

更簡單理解的就是:有一個作業系統為2G,我們分配給兩個執行緒,每個800M,也就還剩400M,這樣的話,有一個執行緒不夠用的話,就會在400裡邊申請,所以如果剩下的越多,出現OOM的可能性越小,如果每個分配950M,這樣就剩100M,這樣的話出現OOM的可能性就更大。如果在增加執行緒,系統對每一個執行緒非配的記憶體是一定的,所以剩下的記憶體更少,這樣的話,出現OOM的可能更大,但這都是相對而說。

遇到OOM這時候我們應該怎麼做:

如果是建立過多執行緒導致的記憶體溢位,在不能減少執行緒數或者更換 64 位虛擬機器的情況下,就只能通過減少最大堆和減少棧容量來換取更多的執行緒。如果沒有這方面的經驗,這種通過“減少記憶體”的手段來解決記憶體溢位的方式會比較難以想到。
 

3:方法區和執行時常量池溢位

永久代溢位——常量池溢位 
要模擬常量池溢位,可以使用String物件的intern()方法。如果常量池包含一個此String物件的字串,就返回代表這個字串的String物件,否則將String物件包含的字串新增到常量池中。

 

4:本地直接記憶體溢位

DirectMemory可以通過-XX:MaxDirectMemorySize指定,如果不指定,預設與Java堆的最大值(-Xmx指定)一樣。 

 

https://www.cnblogs.com/zhuxing/articles/1247621.html

總結: 
棧記憶體溢位:程式所要求的棧深度過大。 
堆記憶體溢位: 分清記憶體洩露還是 記憶體容量不足。洩露則看物件如何被 GC Root 引用,不足則通過調大-Xms,-Xmx引數。 
永久代溢位:Class物件未被釋放,Class物件佔用資訊過多,有過多的Class物件。 
直接記憶體溢位:系統哪些地方會使用直接記憶體。