java中OOM錯誤淺析----(面試可以聊的東西)

嗯,生活加油鴨。。。。 實習中遇到OOM錯誤

GC overhead limit exceeded   問題,所以整理一下OOM異常問題:不對的地方請小夥伴留言^_^

“阿里的開發手冊”對OOM的描述:

OOM,全稱“Out Of Memory”,翻譯成中文就是“內存用完了”意思就是說,當JVM因爲沒有足夠的內存來爲對象分配空間並且垃圾回收器也已經沒有空間可回收時,就會拋出這個error(注:非exception,因爲這個問題已經嚴重到不足以被應用處理)

  • 內存泄露:申請使用完的內存沒有釋放,導致虛擬機不能再次使用該內存,此時這段內存就泄露了,因爲申請者不用了,而又不能被虛擬機分配給別人用。
  • 內存溢出:申請的內存超出了JVM能提供的內存大小,此時稱之爲溢出。

《深入理解java虛擬機》中描述的OOM發生區域

1)Java虛擬機棧:

    描述Java方法執行的內存模型,與計數器一般,java虛擬機棧也是線程私有的,它的生命週期與線程相同,每個方法在執行的同時會創建一個棧幀,用於存儲局部變量表,操作數棧,動態鏈表,方法出口等信息。每個方法從調用到執行完成過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。

  •  局部變量表:存儲了編譯期可知的各種基本數據類型,對象的引用(reference類型,即指向對象起始地址的引用指針)和returnAddress類型(指向一條字節碼指令的地址)。long與double類型數據會佔用2個局部變量空間,其餘的數據類型會佔用一個,局部變量表所需要的內存空間在編譯期完成分配,當進入一個方法時,方法在幀中分配的局部變量空間是完全確定的。在方法運行期不會改變局部變量表的大小。
  • ........

    在Java虛擬機規範中,如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常。如果虛擬機棧可以動態擴展,但擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。

2)本地方法棧:

  •  與虛擬機類似,區別是虛擬機棧爲虛擬機執行的Java方法服務,而本地方法棧則是爲虛擬機使用到的Native方法服務也會拋出StackOverflowError異常,OutOfMemoryError異常

3)Java堆:

  •  java 堆是虛擬機所管理的內存中最大的一塊,java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建,唯一作用是用來存放對象實例,幾乎所有的對象實例以及數組都要在堆上分配地址。
  • java堆是垃圾收集器管理的主要區域。也被稱爲GC堆。Java堆可以處於物理上不連續的內存空間,只用邏輯上連續即可,可以通過-Xmx和Xms控制。如果在堆中沒有內存完成實例分配,並且堆中也無法擴展,會拋出OutOfMenory異常。

4)運行時常量池:

方法區的一部分Class文件中除了有類的版本,字段,方法,接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池中存放。

運行時常量池對於Class文件常量池的一個特徵是具備動態性,Java語言不要求常量一定是在編譯期才產生,即並非預置入Class文件中的常量池的內容可以才能進入方法區運行時常量池,運行期也可以將新的常量放入池中,比如String的intern()方法。當常量池無法 在申請內存時會拋出OutOfMethodError異常。

具體的實例:

一、Java堆溢出:

package com.company;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * @Author: Liruilong
 * @Date: 2019/7/13 10:03
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
 
    static class OOMObject{
    }
    public static void main(String[] args){
        List<OOMObject> list = new ArrayList<>();
 
        while (true){
            list.add(new OOMObject());
        }
    }
}

java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid13048.hprof ...
Heap dump file created [29202866 bytes in 0.128 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    
at java.util.Arrays.copyOf(Arrays.java:3204)    
at java.util.Arrays.copyOf(Arrays.java:3175)    
at java.util.ArrayList.grow(ArrayList.java:246)    
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:220)    
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:212)    
at java.util.ArrayList.add(ArrayList.java:443)    
at com.company.HeapOOM.main(HeapOOM.java:19)

二、虛擬機棧和本地方法棧溢出:

package com.company;
 
/**
 * @Author: Liruilong
 * @Date: 2019/7/13 18:00
 * @VM Args: -Xss128k
 */
public class JavaVMStackSOF {
 
    private int stackLength = 1;
 
    public void stackLeng(){
        stackLength++;
        stackLeng();
    }
 
    public static void main(String[] args)throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try{
            oom.stackLeng();
        }catch (Throwable throwable){
            System.out.println("sstack length:"+oom.stackLength);
            throw throwable;
        }
    }
 
}
sstack length:1001
Exception in thread "main" java.lang.StackOverflowError
    at com.company.JavaVMStackSOF.stackLeng(JavaVMStackSOF.java:13)
    at com.company.JavaVMStackSOF.stackLeng(JavaVMStackSOF.java:14)
        ………………

三、方法區和運行時常量池溢出

 String.intern()是一個Native方法,當字符串對象已經包含一個String的字符串引用時,則返回字符串的引用,反之,將字符串添加到常量池中,併發揮Sting對象的引用。

在JDK1.6之前的版本,由於常量池分配永久代中,所以可以通過限制方法區的大小來限制常量池容量。

package com.company;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * @Author: Liruilong
 * @Date: 2019/7/14 7:05
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimConstantPoolOOM {
 
    public static void main(String[] args) {
        // 使用List保持常量池的引用,避免Full GC回收常量池行爲
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

最後看遇到的問題:

查了一下:

oracle官方給出了這個錯誤產生的原因和解決方法:

Exception in thread thread_name: java.lang.OutOfMemoryError: GC Overhead limit exceeded
Cause: The detail message "GC overhead limit exceeded" indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap and has been doing so far the last 5 (compile time constant) consecutive garbage collections, then a java.lang.OutOfMemoryError is thrown. This exception is typically thrown because the amount of live data barely fits into the Java heap having little free space for new allocations.
Action: Increase the heap size. The java.lang.OutOfMemoryError exception for GC Overhead limit exceeded can be turned off with the command line flag -XX:-UseGCOverheadLimit.

原因:


大概意思就是說,JVM花費了98%的時間進行垃圾回收,而只得到2%可用的內存,頻繁的進行內存回收(最起碼已經進行了5次連續的垃圾回收),JVM就會曝出ava.lang.OutOfMemoryError: GC overhead limit exceeded錯誤。

即 超出了GC開銷限制。是JDK6新添的錯誤類型。是發生在GC佔用大量時間爲釋放很小空間的時候發生的,是一種保護機制。一般是因爲堆太小,導致異常的原因:沒有足夠的內存 

 

經過分析,我使用多線程的解析文件,解析的文件信息存放佔用很大的內存,而使用時,我使用其中一小部分,但是整個內存被佔用着,GC無法進行回收,所以會報錯。我解決的問題是把需要的信息提取出來放到內存裏。減少了對內存的使用。

解決方法:

  • 1、增加heap堆內存。
    •   增大堆內存 set JAVA_OPTS=-server -Xms512m -Xmx1024m -XX:MaxNewSize=1024m -XX:MaxPermSize=1024m 
  • 2、增加對內存後錯誤依舊,獲取heap內存快照,使用Eclipse MAT工具,找出內存泄露發生的原因並進行修復。
  • 3、優化代碼以使用更少的內存或重用對象,而不是創建新的對象,從而減少垃圾收集器運行的次數。如果代碼中創建了許多臨時對象(例如在循環中),應該嘗試重用它們。
  • 4、除了使用命令-xms1g -xmx2g設置堆內存之外,嘗試在啓動腳本中加入配置:

 在啓動腳本中添加-XX:-UseGCOverheadLimit命令。這個方法只會把“java.lang.OutOfMemoryError: GC overhead limit exceeded”變成更常見的java.lang.OutOfMemoryError: Java heap space錯誤。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章