1. 程式人生 > >JVM系列第14講:JVM引數之GC日誌配置

JVM系列第14講:JVM引數之GC日誌配置

說到 Java 虛擬機器,不得不提的就是 Java 虛擬機器的 GC(Garbage Collection)日誌。而對於 GC 日誌,我們不僅要學會看懂,而且要學會如何設定對應的 GC 日誌引數。今天就讓我們來學習一下 Java 虛擬機器中所有與 GC 日誌有關的引數。相信掌握了這些引數之後,對於大家線上列印 GC 日誌是有不少幫助的。

為了能夠更直觀地顯示出每個引數的作用,我們將以下面的 Demo 為例子去設定 GC 日誌引數。

/**
 * @author 陳樹義
 * @date 2018.09.29
 */
public class GCDemo {
    public static void main(String[] args) {
        // allocate 4M space
        byte[] b = new byte[4 * 1024 * 1024];
        System.out.println("first allocate");
        // allocate 4M space
        b = new byte[4 * 1024 * 1024];
        System.out.println("second allocate");
    }
}

在上面的程式中,我們兩次分配了 4M 的記憶體空間。為了認為製造 GC,我們啟動時的 JVM 引數固定加上下面幾個引數:

-XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
  • -XX:+UseSerialGC 表示強制使用Serial+SerialOld收集器組合
  • -Xms20m 表示堆空間初始大小為 20 M。
  • -Xmx20m 表示堆空間最大大小為 20 M。
  • -Xmn10m 表示新生代大小為 10M。
  • -XX:SurvivorRatio=8 表示Eden:Survivor=8:1

經過上面這個設定,此時我們的堆空間的記憶體比例情況如下:Eden區 8M,FromSurvivor 1M,ToSurvivor 1M,老年代 10M。

下面就讓我們來看看油管 GC 的引數有哪些吧。

列印GC日誌

在 GC 日誌引數中,最簡單的一個引數就是列印 GC 日誌:-XX:PrintGC。我們用下面的命令執行程式:

java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGC com.chenshuyi.GCDemo

輸出結果:

first allocate
second allocate
[GC (Allocation Failure)  4767K->4374K(19456K), 0.0045179 secs]

可以看到程式在第一次分配陣列空間的時候發生了 GC,並且把 GC 前後以及堆空間大小都列印了出來。該日誌顯示 GC 前堆空間使用量為 4767K(4M左右)。GC 後堆空間為 4374K,當前可用堆大小為 19456K。

但你會發現使用 PrintGC 引數打印出來的日誌比較簡單,無法檢視更詳細的資訊。如果你要檢視更詳細的資訊,那麼就需要下面這個引數。

列印詳細GC日誌

如果要檢視更加詳細的 GC 日誌,那麼就要使用 -XX:+PrintGCDetils 引數。下面我們使用該引數執行程式:

java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGC com.chenshuyi.GCDemo

程式輸出:

first allocate
second allocate
[GC (Allocation Failure) [DefNew: 4603K->278K(9216K), 0.0036744 secs] 4603K->4374K(19456K), 0.0037100 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 def new generation   total 9216K, used 4538K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)
  from space 1024K,  27% used [0x00000007bf500000, 0x00000007bf545920, 0x00000007bf600000)
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 tenured generation   total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
 Metaspace       used 2649K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

從上面的日誌可以看出,該引數能打印出更加詳細的 GC 資訊,包括:年輕代的資訊、永久代的資訊。

[GC (Allocation Failure) [DefNew: 4603K->278K(9216K), 0.0036744 secs] 4603K->4374K(19456K), 0.0037100 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

該引數還會在退出之前打印出整個堆的詳細資訊:

Heap
 def new generation   total 9216K, used 4538K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)
  from space 1024K,  27% used [0x00000007bf500000, 0x00000007bf545920, 0x00000007bf600000)
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 tenured generation   total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
 Metaspace       used 2649K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

GC前後列印堆資訊

上面兩個命令基本上可以應付 90% 的使用場景了,但有時候我們在 GC 前後還想獲取更加詳細的資訊。那麼我們可以使用 PrintHeapAtGC 引數,該引數會在 GC 前後列印堆資訊。

使用下面的命令執行程式:

java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintHeapAtGC com.chenshuyi.GCDemo

輸出結果:

first allocate
second allocate
{Heap before GC invocations=0 (full 0):
 def new generation   total 9216K, used 4767K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  58% used [0x00000007bec00000, 0x00000007bf0a7e98, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 0K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,   0% used [0x00000007bf600000, 0x00000007bf600000, 0x00000007bf600200, 0x00000007c0000000)
 Metaspace       used 2646K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K
Heap after GC invocations=1 (full 0):
 def new generation   total 9216K, used 278K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,   0% used [0x00000007bec00000, 0x00000007bec00000, 0x00000007bf400000)
  from space 1024K,  27% used [0x00000007bf500000, 0x00000007bf545950, 0x00000007bf600000)
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 tenured generation   total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
 Metaspace       used 2646K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K
}

仔細看一下,會發現在 GC 發生前後都列印了一次堆空間資訊。

通過這個引數,我們可以詳細瞭解每次 GC 時堆空間的詳細資訊。

列印GC發生的時間 -XX:+PrintGCTimeStamps

這個引數非常簡單,就是在每次 GC 日誌的前面加上一個時間戳。這個時間戳表示 JVM 啟動後到現在所逝去的時間。

使用下面的引數執行程式:

java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGC -XX:+PrintGCTimeStamps com.chenshuyi.GCDemo

輸出結果:

first allocate
second allocate
0.130: [GC (Allocation Failure)  4767K->4374K(19456K), 0.0051351 secs]

上面日誌第 3 行中的「0.130」就是該 GC 發生的時間。

-XX:+PrintGCApplicationConcurrentTime 列印應用程式的執行時間

使用下面的命令執行程式:

java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGC -XX:+PrintGCApplicationConcurrentTime com.chenshuyi.GCDemo

執行結果:

first allocate
second allocate
Application time: 0.0371892 seconds
[GC (Allocation Failure)  4767K->4374K(19456K), 0.0040074 secs]
Application time: 0.0010712 seconds

-XX:+PrintGCApplicationStoppedTime 列印應用由於GC而產生的停頓時間

使用下面的命令執行程式:

java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGC -XX:+PrintGCApplicationStoppedTime com.chenshuyi.GCDemo

執行結果:

first allocate
second allocate
[GC (Allocation Failure)  4767K->4374K(19456K), 0.0045644 secs]
Total time for which application threads were stopped: 0.0047873 seconds, Stopping threads took: 0.0000329 seconds

可以看到最後一行打印出了因為 GC 而暫停的時間。

儲存GC日誌 -Xloggc

這個引數可以將 GC 日誌輸出到檔案中儲存起來。

使用下面的引數執行程式:

java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGC -XX:+PrintReferenceGC -Xloggc:gc.log com.chenshuyi.GCDemo

執行之後在本目錄會生成一個 gc.log 檔案,開啟該檔案:

Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for bsd-amd64 JRE (1.8.0_181-b13), built on Jul  7 2018 01:02:31 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 8388608k(45132k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+PrintReferenceGC -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC 
0.124: [GC (Allocation Failure)  4767K->4374K(19456K), 0.0047748 secs]

可以看到堆的相關資訊,以及 GC 的資訊。

總結

除了上面這些引數,還有可以檢視弱引用的引數:-XX:+PrintReferenceGC。它跟蹤軟引用、弱引用、虛引用和Finallize佇列的資訊,但是使用場景較為狹窄。基本上掌握上面的幾個常用的 GC 日誌引數就足夠排查使用,最重要的是弄清楚每個引數的作用和用法。

最後用列表的形式總結一下,加深一下印象。

引數 含義
-XX:PrintGC 列印GC日誌
-XX:+PrintGCDetails 列印詳細的GC日誌。還會在退出前列印堆的詳細資訊。
-XX:+PrintHeapAtGC 每次GC前後列印堆資訊。
-XX:+PrintGCTimeStamps 列印GC發生的時間。
-XX:+PrintGCApplicationConcurrentTime 列印應用程式的執行時間
-XX:+PrintGCApplicationStoppedTime 列印應用由於GC而產生的停頓時間
-XX:+PrintReferenceGC 跟蹤軟引用、弱引用、虛引用和Finallize佇列。
-XLoggc 將GC日誌以檔案形式輸出。