JVM OOM分析與調優

OutOfMemoryError
除了程序計數器外,其他的幾個運行數據區都有可能發生OutOfMemoryError(OOM)的可能。
所以在遇到OOM的問題時應能根據異常的信息快速定位到時哪一個內存區域的內存溢出,知道什麼樣的代碼會致使OOM,以及該如何處理。 
 
一、Java堆溢出
  Heap堆是OOM故障最主要的發源地,它存儲着幾乎全部的實例對象。在線上生產環境中,JVM的Xms和Xmx應設置成同樣的大小 ,避免在GC後調整堆大小時帶來額外的壓力。
  模擬:不停的建立對象,而且GC Roots到對象之間有可達路徑,超過堆的最大內存容量就會OOM。 如,在while(true)循環中不停的建立對象並將對象add到List集合中
 
  Java堆內存溢出時異常堆棧信息會在「java.lang.OutOfMemoryError」後提示Java heap space
 
  如何定位?
    1)設置參數-XX:+HeapDumpOnOutOfMemoryError 來配置當內存耗盡時記錄下當時的內存快照,通常根據MAT分析具體緣由是內存泄漏仍是內存溢出
    2)內存泄漏根據工具查看泄漏對象到GC Roots的引用鏈,找到泄漏對象時經過怎麼樣的路徑與GC Roots相關聯並致使垃圾收集器沒法自動回收它們的。根據對象類型和GC Roots引用鏈就能夠準肯定位到泄漏代碼的位置。
    3)內存溢出檢查虛擬機參數堆參數可否擴大,代碼上檢查是否存在某些對象生命週期過長、持有狀態時間過長。
 
二、虛擬機棧和本地方法棧溢出
  HotSpot虛擬機不區分虛擬機棧和本地方法棧,所以設置本地方法棧的參數-Xoss其實是無效的,棧的參數只能根據-Xss設定
  若是線程請求的棧深度超過了JVM容許的最大深度,將拋出StackOverflowError   
  若是虛擬機在擴展棧時沒法申請到足夠的內存將跑出OutOfMemoryError
  1)StackOverflowError   容易定位,多是遞歸沒有出口,正常的方法調用深度在1000-2000之間徹底沒有問題
  2)OOM多是創建的線程數量很是多,每一個線程瓜分棧內存,當線程都存活着沒有足夠的內存去分配給線程時會拋出OOM
 
三、方法區和運行時常量池溢出
  Java方法區和運行時常量池溢出異常堆棧信息會在「java.lang.OutOfMemoryError」後提示PermGen space
  如String字符串存放在常量池中
 
四、本機直接內存溢出
  Unsafe的allocateMemory能夠申請分配內存(Unsafe實例可經過反射獲取)
  由DirectMemory致使的內存溢出在Heap Dump文件中看不出明顯的異常,若是OOM以後的Dump文件很小,而程序中直接或者間接使用了NIO,那多是這方面的緣由。
  DirectMemory-XX:MaxDirectMemory指定,若是不指定默認與Java堆內存最大值同樣。
  垃圾收集時,虛擬機 只有在Full GC時會順便回收DirectMemory中廢棄的對象。所以DirectMemory內存滿了以後,只有等待系統的下一次Full GC,或者拋出內存異常在catch中調用System.gc()
 
 
內存溢出排查緣由

  一、GC日誌分析html

  爲了在內存溢出時排查緣由,能夠在JVM啓動時加一些參數來控制,當JVM內存出問題時能夠經過分析記錄下來的GC日誌,GC的頻率和每次GC回收了哪些內存。java

 GC的日誌輸入有如下參數linux

  1)-verbose:gc  能夠輔助輸出一些詳細的GC信息sql

  2)-XX:+PrintGCDetails  輸出GC的詳細信息數組

  3)-XX:+PrintGCApplicationStoppedTime  輸出GC形成應用程序暫停的時間瀏覽器

  4)-XX:+PrintGCDateStamps  輸出GC發生的時間信息服務器

  5)-XX:PrintHeapAtGC  在GC先後輸出堆中各個區域的大小eclipse

  6)-Xloggc:[file]  將GC信息輸出到單獨的文件工具

 

每種GC方式輸出日誌的形式不一樣,除CMS的日誌和其餘GC方式差別較大外,其他GC方式的日誌能夠抽象成以下方式性能

  [GC [<collector>: <starting occupancy1> -> <ending occupancy1> (total size1) , <pause time1> secs]

  <starting occupancy2> -> <ending occupancy2> (total size2) , <pause time2> secs] ]

說明以下

  1)<collectot>GC  表示垃圾收集器的名稱

  2)<starting occupancy1>  表示Young區在GC前佔用的內存

  3)<ending occupancy1>  表示Young區在GC後佔用的內存

  4)(total size1)  表示Young區的總內存大小

  5)<pause time1>   表示Young區局部收集時JVM暫停處理的時間 secs表示單位秒

  6)<starting occupancy2>   表示Heap在GC前佔用的內存

  7)<ending occupancy2>  表示Heap在GC後佔用的內存

  8)(total size2)  表示Heap的總內存

  9)<pause time2>  表示在GC過程當中JVM暫停處理的總時間

 

能夠根據日誌來判斷是否存在內存泄漏的問題:

  <starting occupancy1> - <ending occupancy1>  和 <starting occupancy2> - <ending occupancy2> 比較

  一、若是前者差等於後者差,代表Young區GC 對象100%被回收,沒有對象進入 Old區或者Perm區

  二、若是前者大於後者,那麼差值就是此次GC對象進入Old或者Perm區的大小

  若是隨着時間的的延長,<ending occupancy2>的大小一直在增加,並且Full GC很頻繁,那麼極可能就是內存泄漏致使的。

 

 

  二、堆快照文件分析

   如下Java命令在JDK的bin目錄下執行

    經過命令   jmap -dump:format=b,file=[filename][pid]  jmap(Memory Map for Java)

來記錄下堆的內存快照,而後利用第三方工具如eclipse 插件MAT來分析整個Heap的對象關聯狀況。

  若是內存耗盡可直接致使JVM退出,能夠經過參數

  -XX:+HeapDumpOnOutOfMemoryError 來配置當內存耗盡時記錄下當時的內存快照

  -XX:HeapDumpPath  指定內存快照文件的路徑  文件快照的名稱格式爲 java_[pid].hprof

 

  若是是OOM,可能有兩方面的緣由

  一、內存分配太小,不知足程序運行所須要的內存

  二、內存泄漏(FullGC頻繁,回收後Heap佔用的內存不斷增加)

 

  三、JVM Crash 日誌分析

  TODO

 

JVM性能監控和故障處理 

  經過工具導出和處理分析 運行日誌、異常堆棧、GC日誌、線程快照(threaddump/javacore文件)、堆轉儲快照(headdump/hprof文件)等

  jps:JVM process status tool,顯示指定系統內全部的HotSpot虛擬機進程

  jstat:JVM statistics Monitoring Tool,收集HotSpot虛擬機各方面的運行數據

  jinfo:Configuration Info for java,顯示虛擬機配置信息

  jmap:Memory map for Java,生成虛擬機的內存轉儲快照(heapdump文件)

  jhat:JVM Heap Dump Browser,用於分析heapdump文件,它會創建一個http/html服務器,讓用戶能夠在瀏覽器上查看分析結果

  jstack:Stack trace for Java,顯示虛擬機的線程快照

 

  jps:(Jvm Process Status )和linux中ps命令類似,可列出正在運行的虛擬機進程,並顯示虛擬機執行主類和這些進程的惟一ID(Local Virtual Machine Identifier LVMID)

  格式:jps [option] [hostid(主機名)]

  參數:

    -l  輸出主類的全路徑  

    -v  輸出虛擬機進程啓動是的JVM參數

   

  jstat:用於監視虛擬機各類運行狀態信息的命令行工具,它能夠顯示本地或者遠程虛擬機進程中的類加載、內存、垃圾收集、JIT編譯等運行數據,是定位虛擬機性能問題的首選工具。

  格式:jstat [ option vmid [interval] [count] ]

  interval和count是查詢間隔和次數,若是忽略這兩個參數則只查詢一次

  選項:

    -class 監視類裝載、卸載數量、總空間以及類裝載消耗時間

    -gc 監視Java堆情況,包含Eden區,兩個Survivor區、老年代、永久代等的容量,已用空間,GC時間等信息。

 

  jmap:Java內存映射工具,用於生成堆存儲快照heapdump,生成堆快照還能夠經過設置參數使在OOM異常以後自動生成堆dump文件。

  jmap還可查詢finalize執行隊列、Java堆和永久代的詳細信息(空間使用率,使用哪一種收集器等)。

  格式:jmap [option] vmid

  option參數

    -dump:format=b,file=[filename] 生成堆轉儲快照

    -heap  顯示Java堆詳細信息,如使用哪一種收集器、參數配置、分代情況等。

 

  jhat:與jmap搭配使用,分析jmap生成的堆轉儲快照文件。通常不會直接使用jhat命令分析dump文件,一是不會直接在應用服務器上分析dump文件,由於分析耗時耗資源,二是jhat分析結果比較簡陋,可用VisualVM,MAT等工具

 

  jstack:Java堆棧跟蹤工具,用戶生成虛擬機當前時刻的線程快照,線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成快照的主要目的是定位線程出現長時間等待的緣由,如線程間死鎖、死循環、請求外部資源(如sql)致使的長時間等待等

  線程出現停頓的時候經過jstack來查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作什麼事情或者等待什麼資源

  格式:jstack [option] vmid

  option參數:

    -F:強制輸出線程堆棧

    -l:除堆棧外顯示關於鎖的附加信息

    -m:可顯示調用本地方法的堆棧

  JDK1.5以後的Thread類新增了getAllStackTraces()方法用戶獲取虛擬機中全部線程的StackTraceElement對象。和jstack功能相似

 public static Map<Thread,StackTraceElement[]> getAllStackTraces()   返回從 ThreadStackTraceElement 數組的一個 Map,表明相應線程的堆棧跟蹤。 、

 
總結: 
 
  若是要定位OOM問題使用jps和jmap組合命令,先用jps或者linux的ps命令查看虛擬機進程的vmid,而後用jamp命令生成堆快照文件,最後使用工具分析dump文件定位問題。

  若是要定位線程響應時間過長的問題,使用jps和jstack命令,先用jps或者linux的ps命令查看虛擬機進程的vmid,而後用jstack命令查看線程堆棧信息

 

 

JVM調優

JVM調優是經過分析GC日誌等來分析java內存和垃圾回收的狀況,來調整各內存區域內存佔比和垃圾回收策略。

GC優化的根本緣由
  垃圾收集器的工做就是清除Java建立的對象,垃圾收集器須要清理的對象數量以及要執行的GC數量均取決於已建立的對象數量。所以,爲了使你的系統在GC上表現良好,首先須要在保證功能的基礎上減小建立對象的數量。
  如:養成以下的良好的編碼習慣
    一、使用StringBuffer或StringBuilder來代替String
    二、儘可能少輸出日誌

GC優化的兩個目的
  一、減小Full GC的頻率
  二、減小Full GC的執行時間

一、減小Full GC的頻率——將進入老年代的對象數量降到最低
  充分使用系統資源,減小GC停頓時間和停頓次數,因爲Full GC的停頓時間遠比Minor GC的停頓時間長,所以要控制Full GC的頻率。
控制Full GC的頻率的關鍵是將進入老年代的對象數量降到最低。所以要看應用中的絕大多數對象是否符合「朝生夕滅」的原則,即大多數的對象的生存時間都不該太長,尤爲是不能有成批量的、長時間存活的對象產生,這樣這些對象在Minor GC就會被回收,不會進入老年代,這樣才能保證老年代的穩定。

 

好比對於十幾小時乃至一天才出現一次Full GC的系統能夠經過定時任務的方式在夜間觸發Full GC。

 

若是FullGC次數過多多是下面的緣由:
  一、內存佔用高:代碼中建立了大量的對象致使內存泄漏,不能回收內存,建立新對象致使空間不足觸發fullGC
  二、內存佔用不高:多是顯示的調用System.gc()次數太多致使的fullGC,能夠經過添加-XX:+DisableExplicitGC來禁用JVM對顯式GC的響應

 

二、減小Full GC的時間
  Full GC的停頓時間遠比Minor GC的停頓時間長,所以,若是在Full GC上花費過多的時間(超過1s), 將可能出現超時錯誤。

    1)若是經過減少老年代內存來減小Full GC時間,可能會引發OutOfMemoryError或者致使Full GC的頻率升高。

    2)若是經過增長老年代內存來下降Full GC的頻率,Full GC的時間可能所以增長。

    所以,你須要把老年代的大小設置成一個「合適」的值。

    

    影響GC性能的因素:

      1)-XX:NewRatio 新生代和老年代的內存比(默認1:2),這個參數將對GC性能產生重要的影響

      2)垃圾收集器的類型 

    何時須要進行GC優化?  經過監控GC狀態,而後分析GC監控結果來判斷是否須要進行GC優化。  若是GC執行時間知足下列全部條件,就沒有必要進行GC優化了:    1)Minor GC執行很是迅速(50ms之內)    2)Minor GC沒有頻繁執行(大約10s執行一次)    3)Full GC執行很是迅速(1s之內)    4)Full GC沒有頻繁執行(大約10min執行一次)    GC優化是一個不斷嘗試並逐漸調試的過程。在每次設置完GC參數後,要收集數據,並收集至少24個小時以後再進行結果分析。
相關文章
相關標籤/搜索