Java OOM問題如何排查

OOM 問題

什麼是OOM

OOM爲out of memory的簡稱,來源於java.lang.OutOfMemoryError,指程序需要的內存空間大於系統分配的內存空間,OOM後果就是程序crash;可以通俗理解:程序申請內存過大,虛擬機無法滿足,然後自殺了。

導致OOM問題的原因

爲什麼會沒有內存了呢?原因不外乎有兩點:

1)分配的少了:比如虛擬機本身可使用的內存(一般通過啓動時的VM參數指定)太少。

2)應用用的太多,並且用完沒釋放,浪費了。此時就會造成內存泄露或者內存溢出。

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

內存溢出:申請的內存超出了JVM能提供的內存大小,此時稱之爲溢出。

最常見的OOM情況有以下三種:

  • java.lang.OutOfMemoryError: Java heap space ------>java堆內存溢出,此種情況最常見,一般由於內存泄露或者堆的大小設置不當引起。對於內存泄露,需要通過內存監控軟件查找程序中的泄露代碼,而堆大小可以通過虛擬機參數-Xms,-Xmx等修改。
  • java.lang.OutOfMemoryError: PermGen space 或 java.lang.OutOfMemoryError:MetaSpace ------>java方法區,(java8 元空間)溢出了,一般出現於大量Class或者jsp頁面,或者採用cglib等反射機制的情況,因爲上述情況會產生大量的Class信息存儲於方法區。此種情況可以通過更改方法區的大小來解決,使用類似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,過多的常量尤其是字符串也會導致方法區溢出。
  • java.lang.StackOverflowError ------> 不會拋OOM error,但也是比較常見的Java內存溢出。JAVA虛擬機棧溢出,一般是由於程序中存在死循環或者深度遞歸調用造成的,棧大小設置太小也會出現此種溢出。可以通過虛擬機參數-Xss來設置棧的大小。

排查手段

一般手段是:先通過內存映像工具對Dump出來的堆轉儲快照進行分析,重點是確認內存中的對象是否是必要的,也就是要先分清楚到底是出現了內存泄漏還是內存溢出。

  • 如果是內存泄漏,可進一步通過工具查看泄漏對象到GC Roots的引用鏈。這樣就能夠找到泄漏的對象是通過怎麼樣的路徑與GC Roots相關聯的導致垃圾回收機制無法將其回收。掌握了泄漏對象的類信息和GC Roots引用鏈的信息,就可以比較準確地定位泄漏代碼的位置。

  • 如果不存在泄漏,那麼就是內存中的對象確實必須存活着,那麼此時就需要通過虛擬機的堆參數( -Xmx和-Xms)來適當調大參數;從代碼上檢查是否存在某些對象存活時間過長、持有時間過長的情況,嘗試減少運行時內存的消耗。

實戰

接下來用一個簡單的案例,展示OOM問題排查過程

public class OomDemo {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder();
        while(true){
            stringBuilder.append(System.currentTimeMillis());
        }
    }
}
  • 執行代碼時,通過設置JVM參數達到OOM的目的
 java -Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=oom.hprof OomDemo 

-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath參數分別用於指定發生OOM是否要導出堆以及導出堆的文件路徑

這個方法也可以通過jmap實時生成

jmap -dump:format=b,file=$java_pid.hprof     #java_pid爲java進程ID

以上命令執行後,程序會出現如下錯誤:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to oom.out ...
Heap dump file created [3196858 bytes in 0.016 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:3332)
        at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
        at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:700)
        at java.lang.StringBuilder.append(StringBuilder.java:214)
        at jvm.OomDemo.main(OomDemo.java:13)

MAT分析

首先使用MAT打開剛剛導出的hprof文件,選擇報告裏的泄露嫌疑分析 Leak Suspects Report

image-20200615114346988

可以看到有一個本地變量,站了總存儲的92%,實際佔用的是char[],See stacktrace,可看到該對象所在線程的堆棧信息:

image-20200615115212703

通過這兒,可以定位到發生OOM的代碼段,至此,可根據具體代碼具體分析。

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