動態

詳情 返回 返回

JVM頻繁GC內存溢出排查 - 動態 詳情

前言
GC(Garbage collection)頻繁和堆內存溢出原因簡單來説是對象佔用堆空間難以回收,新對象無法分配觸發GC或者直接導致內存溢出,最終進程結束。
排查思路是先查看進程各種類型對象佔用空間大小和比例,鎖定佔用空間較多的對象後再分析相關的程序是否有使用不當的地方。下文的側重點是通過多種方式查看堆內存分佈。
例子程序
先編譯(javac FrequentFullGCSample.java)例子程序,執行下面的指令來創建JVM進程。
指定-Xms2M -Xmx2M來限制堆內存大小為 2兆,作為例子程序夠用了;為了方便演示我們再加上這兩個選項:-XX:+PrintCommandLineFlags打印有變動的JVM選項值;-XX:+PrintGCDetails打印垃圾回收的詳細信息。
java -Xms2M -Xmx2M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails FrequentFullGCSample
複製代碼
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;

public class FrequentFullGCSample {

   private static final Queue<Object> QUEUE = new LinkedList<>();

   public static void main(String[] args) throws InterruptedException {
       while (true) {
           QUEUE.offer(new Object());
           TimeUnit.MILLISECONDS.sleep(3);
      }
  }
}
複製代碼
從控制枱日誌可以看到首先輸出了部分有變動的JVM選項值:初始堆(-XX:InitialHeapSize)和最大堆(-XX:MaxHeapSize)大小都為2097152字節(B),也就是2兆(M);使用了類指針壓縮(-XX:+UseCompressedClassPointers)和對象指針壓縮(-XX:+UseCompressedOops)。
在後面我們可以看到輸出的GC日誌信息,對象先分配在年輕代,空間佔滿開始頻繁YGC(Young garbage collection)。

圖片

經過幾代回收之後,對象分代年齡增長轉移到老年代,老年代空間逐漸佔滿開始頻繁Full GC。

圖片

由於例子程序新創建的對象都是強引用類型,不能定為垃圾,無法回收。經過頻繁的Full GC無果後新對象無法分配到空間,報錯內存溢出(java.lang.OutOfMemoryError)。從後面打印的各個分代的內存使用情況可以看出年輕代(def new generation)和老年代(tenured generation)佔用比例接近100%。

圖片

先通過jps找到我們的JVM進程ID

圖片

查看堆中對象分配信息
jmap -histo(推薦)
jmap是JDK自帶的指令可以查看堆內存相關的信息。jmap -histo <Java進程ID>會列出進程中每個類型對象實例數量和佔用空間大小按降序列出來,我們再加上head -10來查看前10行信息可以看到排名前幾位的對象相關信息。
這種方式合適進程還在運行的時候用,統計輸出的信息不多,對進程本身影響比較小。
jmap -histo 20091 | head -10
複製代碼

圖片

jmap -dump + Java VisualVM
jmap -dump可以將堆內存信息以二進制的方式轉儲到文件,格式為jmap -dump:format=b,file=<文件名> <Java進程ID>。
這種方式轉儲的數據比較多,當我們的JVM堆內存使用空間比較大的時候(比如上G大小)需要一定的時間,而且轉儲的過程中會影響進程運行。一般不建議這樣做,除非JVM配置/使用的空間比較小。
jmap -dump:format=b,file=71976.dump 71976
複製代碼

圖片

有了堆轉儲文件,我們找一個可以分析這個文件的工具來查看,這裏我們用JDK自帶的JVM圖形化工具Java VisualVM。

Mac在命令行輸出下面命令打開

/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/jvisualvm

Windows在JDK中bin目錄下

複製代碼
點擊【文件】>【裝入】,選擇合適的類型後再選中我們的堆轉儲文件打開。

圖片

裝入後點擊類,可以在圖形化界面看到當時堆中各種類的實例數和大小。

圖片

內存溢出時自動生成堆轉儲文件(推薦)
啓動時增加一個JVM選項-XX:+HeapDumpOnOutOfMemoryError。默認文件會生成到當前目錄下,名為java_pid<進程ID>.hprof,可以通過選項-XX:HeapDumpPath=<目錄>生成到指定的目錄。再用Java VisualVM裝入查看即可。
這種方式是比較推薦的,進程已經出問題要退出了,退出前能生成信息幫助我們事後分析。
java -Xms2M -Xmx2M \
-XX:+PrintCommandLineFlags \
-XX:+PrintGCDetails \
-XX:+HeapDumpOnOutOfMemoryError FrequentFullGCSample
複製代碼
Java VisualVM 遠程監控
java命令中多加幾個選項啓用遠程監控。-Djava.rmi.server.hostname填本機IP地址;-Dcom.sun.management.jmxremote.port填遠程訪問使用的端口;其他兩個是為了關閉鑑權不校驗用户名密碼。
進程開啓監控肯定需要額外的工作,下發數據、網絡交互等等,會影響到進程的運行,所以一般不用。但是可以用在測試的時候,壓力測試同時監控,便於分析。
java -Xms2M -Xmx2M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.port=1099 \
-Djava.rmi.server.hostname=192.168.252.132 FrequentFullGCSample
複製代碼
雙擊【遠程】,填好主機地址後點【確定】。

圖片

右鍵點擊剛剛配置出現的主機,選擇【添加JMX】連接,填好端口號點【確定】。

圖片

雙擊剛配置的JMX連接,點擊【抽樣器】>【內存】可以實時看到堆內存中各種對象的分配情況。

圖片

總結
以上方式推薦的還是jmap -histo和內存溢出時自動轉儲,對進程本身影響比較小。
另外jmap -heap <Java進程ID>可以看堆內存相關的JVM配置和堆內存整體分配。

user avatar code500g 頭像 shawn_5dd516205b8bd 頭像 coderdd 頭像 songhuijin 頭像 niuqh 頭像 kanjianliao 頭像 wenxu_5f1ac00814c86 頭像 grow-with-the-times 頭像 river97 頭像 jump_and_jump 頭像 hubert-style 頭像 minisayo 頭像
點贊 14 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.