java OOM還在看log日誌,兄弟你錯的的很嚴重,正確方式是分析dump文件

應用程序出現OOM異常,你是否仍然通過看日誌的方式去排查問題(該方式定位解決問題是大概率的巧合而已)?正確的排查方案是進行dump文件分析,你知道爲什麼嗎?

OOM異常--intsmaze

首先說一下,本人在開發中遇到的OOM異常基本也是通過看log日誌去定位的(很多OOM異常是因爲出現死循環或者查詢返回的數據量多大,沒有分頁等等,通過異常日誌我們確實能很快定位,但這不是正確的姿勢。),只是碰巧剛好日誌打印的異常棧信息就是對應的代碼問題。
很多博客也說了,定位OOM異常通過分析dump日誌,因此深表疑惑,爲什麼明明看log日誌就能解決的非要去分析dump日誌,網上也沒有檢索到滿意的答案,問了身邊的很多開發,也僅僅說dump進行性能分析,log日誌進行異常排查。在我幾度深思中,突然開竅,特此寫下原因。

正確姿勢dump文件分析--intsmaze

請看大屏幕:

public class OOMDump {

    static class OOMIntsmaze {
        public byte[] placeholder = new byte[64 * 1024];
    }

    public static void fillHeap(ArrayList<OOMIntsmaze> list, int num) throws Exception {

        for (int i = 0; i < num; i++) {
            list.add(new OOMIntsmaze());
            System.out.println(i);
        }
    }

    public static void main(String[] args) throws Exception {
        ArrayList<OOMIntsmaze> list = new ArrayList<OOMIntsmaze>();
        fillHeap(list,131);
        Thread.sleep(20000000);
    }
}

我們配置jvm參數如下 -Xmx10M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d://
當fillHeap(list,131)時,程序正常執行;當fillHeap(list,132)時,程序就會報OOM異常:

130
java.lang.OutOfMemoryError: Java heap space
Dumping heap to d://\java_pid10000.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at cn.intsmaze.dump.OOMDump$OOMIntsmaze.<init>(OOMDump.java:27)
	at cn.intsmaze.dump.OOMDump.fillHeap(OOMDump.java:34)
	at cn.intsmaze.dump.OOMDump.main(OOMDump.java:47)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
Heap dump file created [10195071 bytes in 0.017 secs]
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

通過異常日誌我們可以看到,是因爲代碼
at cn.intsmaze.dump.OOMDump.fillHeap(OOMDump.java:34)
list.add(new OOMIntsmaze());
導致的問題,通過日誌所見即所得,我立馬解決了問題,爲什麼要看dump日誌呢?我有病啊。

其實不然,騷年。假如main方法如下,執行

    public static void main(String[] args) throws Exception {
        ArrayList<OOMIntsmaze> list = new ArrayList<OOMIntsmaze>();
        fillHeap(list,131);
        Map<String,OOMIntsmaze> map=new HashMap<String,OOMIntsmaze>();
        map.put("LIUYANG",new OOMIntsmaze());
        map.put("intsmaze",new OOMIntsmaze());
        Thread.sleep(20000000);
    }

這個時候我們通過異常日誌發現是因爲map.put("LIUYANG",new OOMIntsmaze());導致的,找到代碼發現,map裏面才插入了一條數據,沒有出現死循環,怎麼會導致OOM異常了,真是活久見了。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at cn.intsmaze.dump.OOMDump$OOMIntsmaze.<init>(OOMDump.java:27)
	at cn.intsmaze.dump.OOMDump.main(OOMDump.java:49)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

設置10M我們可以發現list添加132各個元素時會發生OOM,這個時候我們向list添加131個元素,然後執行map添加,發現map添加一個元素就報錯。此時的oom異常日誌定位的是map添加元素導致的。
但是真實情況不是的,因爲看代碼也會發現map只添加了2個元素,怎麼會是他造成的。map的添加只是剛好此時jvm內存達到容量上限了。
所以要找到根本問題,是需要通過dump文件分析OOM時,各個對象的容量狀態。
而且實際情況中,map.put()的代碼並不會向上面示例一樣和list.add()代碼放在一塊,而是位於不同的包下,不同的業務流程中。這個時候看log日誌去定位基本不可能了。
但是爲什麼大家出行OOM異常還是通過看log日誌而且定位的位置是正確的。只是因爲向list.add這種循環中,一直在執行,基本大概率是他觸發的。

正確的姿勢--intsmaze

所以爲了防患於未然,程序猿在開發的時候,一定要配置jvm啓動參數HeapDumpOnOutOfMemoryError。
參數-XX:+HeapDumpOnOutOfMemoryError可以讓虛擬機在出現內存溢出異常時Dump出當前的內存堆轉儲快照以便事後進行分析

dump丟失打印--intsmaze

有些時候,我們的應用程序宕機,既不會打印log日常信息,dump文件也不會生成,這個時候基本就是linux系統殺掉了我們的應用程序進程。

查看/var/log/messages文件

messages 日誌是核心系統日誌文件。它包含了系統啓動時的引導消息,以及系統運行時的其他狀態消息。在messages裏會出現以下信息

out of memory:kill process 8398(java) score 699 or sacrifice child
killed process 8398,UID 505,(java) total-vm:2572232kB,anno-rss:1431292kB,file-rss:908kB

oom killer是linux系統的一個保護進程,當linux系統所剩的內存空間不足以滿足系統正常運行時,會觸發。oomkiller執行時,會找出系統所有線程的score值最高的那個pid,然後幹掉。
這裏我們可以看到,JAVA進程的確是被LINUX的oom killer幹掉了。

我們的應用程序和日誌都只能記錄JVM內發生的內存溢出。如果JVM設置的堆大小超出了操作系統允許的內存大小,那麼操作系統會直接殺死進程,這種情況JVM就無法記錄本次操作。
Linux對於每個進程有一個OOM評分,這個評分在/proc/pid/oom_score文件中。例如/proc/8398/oom_score,如果不希望殺死這個進程,就將oom_adj內容改爲-17。
更多關於linux的oom killer機制請自行百度檢索。

最正確的姿勢:首先調整JVM的heap大小,使得JVM的OOM優先於操作系統的OOM出現,接着設置運行參數,在發生OOM的時候輸出heapdump文件。

哪些內存溢出會產生dump文件--intsmaze

請上公交車:JVM各種內存溢出是否產生dump https://blog.csdn.net/stevendbaguo/article/details/51366181

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