詳盡的Java垃圾回收

1.總覽

這個教程我們將看看詳盡的Java垃圾回收。接下來,我們將介紹幾個不同的實例,我們將瞭解可用的不同配置選項。此外,我們還將重點介紹如何解釋詳細日誌的輸出。

要了解有關垃圾回收 (GC) 和可用不同實現的更多信息,請查看我們有關 Java 垃圾回收器的文章

2.Verbose GC 簡介

在調試和分析許多問題(尤其是內存問題時),通常需要打開詳細的垃圾回收日誌記錄。事實上,有些人會認爲,爲了嚴格監視我們的應用程序運行狀況,我們應該始終監視 JVM 的垃圾回收性能。

正如我們將看到的,GC 日誌是一個非常重要的工具,用於揭示對應用程序的堆和 GC 配置的潛在改進。對於發生的每個 GC,GC 日誌都提供有關其結果和時間的精確數據。

隨着時間的推移,分析這些信息可以幫助我們更好地瞭解行爲或應用程序,並幫助我們調整應用程序的性能。此外,它還可以通過指定最佳堆大小、其他 JVM 選項和備用 GC 算法來幫助優化 GC 頻率和收集時間。

2.1 例子

public class Application {
 
    private static Map<String, String> stringContainer = new HashMap<>();
 
    public static void main(String[] args) {
        System.out.println("Start of program!");
        String stringWithPrefix = "stringWithPrefix";
 
        // Load Java Heap with 3 M java.lang.String instances
        for (int i = 0; i < 3000000; i++) {
            String newString = stringWithPrefix + i;
            stringContainer.put(newString, newString);
        }
        System.out.println("MAP size: " + stringContainer.size());
 
        // Explicit GC!
        System.gc();
 
        // Remove 2 M out of 3 M
        for (int i = 0; i < 2000000; i++) {
            String newString = stringWithPrefix + i;
            stringContainer.remove(newString);
        }
 
        System.out.println("MAP size: " + stringContainer.size());
        System.out.println("End of program!");
    }
}

如上例所示,此簡單程序將 300 萬個 String 實例加載到 Map 對象中。然後,我們使用 System.gc() 對垃圾回收器進行顯式調用。

最後,我們從地圖中刪除 200 萬個字符串實例。我們還顯式使用 System.out.println() 使解釋輸出更加容易。

在下一節中,我們將瞭解如何激活 GC 日誌記錄。

2.激活GC日誌

讓我們首先運行我們的程序,並通過我們的 JVM 啓動參數啓用verbose GC:

-XX:+UseSerialGC -Xms1024m -Xmx1024m -verbose:gc

這裏的重要參數是 -verbose:gc,它以最簡單的形式激活垃圾回收信息的日誌記錄。默認情況下,GC 日誌寫入粗壯,應爲每個年輕代 GC 和每個完整 GC 輸出一行。

爲了我們的示例,我們通過參數-XX:_UseSerialGC 指定了串行垃圾回收器,即最簡單的 GC 實現。

我們還設置了最小和最大的堆大小 1024mb,但當然,我們可以調整更多的 JVM 參數。

3.1Verbose GC的輸出

Start of program!
[GC (Allocation Failure)  279616K->146232K(1013632K), 0.3318607 secs]
[GC (Allocation Failure)  425848K->295442K(1013632K), 0.4266943 secs]
MAP size: 3000000
[Full GC (System.gc())  434341K->368279K(1013632K), 0.5420611 secs]
[GC (Allocation Failure)  647895K->368280K(1013632K), 0.0075449 secs]
MAP size: 1000000
End of program!

在上面的輸出中,我們已經看到了許多有關JVM內部情況的有用信息。

起初,這個輸出看起來相當令人害怕的,但現在讓我們一步一步地分析它。

首先,我們可以看到,四個集合發生, one Full GC and three cleaning Young generation

3.2 verbose GC更詳盡的輸出

讓我們更詳細地分解輸出行,以準確瞭解發生了什麼:

  1. GC or Full GC - 垃圾回收的類型,用於區分次要或完全垃圾回收的 GC 或完整 GC
  2. (Allocation Failure) or (System.gc()) - 集合的原因 - 分配失敗表示Egen去中沒有剩餘空間來分配我們的對象
  3. 279616K->146232K - GC 運行前後佔用的堆內存(用箭頭分隔)
  4. (1013632K) - 堆的當前容量
  5. 0.3318607 秒 - GC 事件的運行時間(以秒爲單位)

我們看輸出的第一行, 279616K->146232K(1013632K) 表明 GC 把佔用的堆內存從 279616K 減少到 146232K,釋放了1013632k的空間。GC的時候,堆空間是1013632K,GC耗費了 0.3318607 s。

儘管簡單的 GC 日誌記錄格式可能很有用,它提供了有限的詳細信息。但是,我們無法判斷 GC 是否將任何對象從新生代移動到老年代,或者每個回收前後新生代的容量是多少。

因此,詳細的 GC 日誌記錄比簡單的日誌記錄更有用。

4.詳細的GC日誌

-XX:+PrintGCDetails,這個給我們更詳細的GC信息,比如:

  • 每次GC前後,新生代和老年代的大小
  • 在新生代和老年代,每次GC消耗的時間
  • 每次GC,提升的對象大小
  • 整個堆內存的大小

5.分析更詳盡的GC日誌

配置參數:

-XX:+UseSerialGC -Xms1024m -Xmx1024m -verbose:gc -XX:+PrintGCDetails

輸出如下:

Start of program!
[GC (Allocation Failure) [DefNew: 279616K->34944K(314560K), 0.3626923 secs] 279616K->146232K(1013632K), 0.3627492 secs] [Times: user=0.33 sys=0.03, real=0.36 secs] 
[GC (Allocation Failure) [DefNew: 314560K->34943K(314560K), 0.4589079 secs] 425848K->295442K(1013632K), 0.4589526 secs] [Times: user=0.41 sys=0.05, real=0.46 secs] 
MAP size: 3000000
[Full GC (System.gc()) [Tenured: 260498K->368281K(699072K), 0.5580183 secs] 434341K->368281K(1013632K), [Metaspace: 2624K->2624K(1056768K)], 0.5580738 secs] [Times: user=0.50 sys=0.06, real=0.56 secs] 
[GC (Allocation Failure) [DefNew: 279616K->0K(314560K), 0.0076722 secs] 647897K->368281K(1013632K), 0.0077169 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
MAP size: 1000000
End of program!
Heap
 def new generation   total 314560K, used 100261K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
  eden space 279616K,  35% used [0x00000000c0000000, 0x00000000c61e9370, 0x00000000d1110000)
  from space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330188, 0x00000000d5550000)
  to   space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
 tenured generation   total 699072K, used 368281K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
   the space 699072K,  52% used [0x00000000d5550000, 0x00000000ebcf65e0, 0x00000000ebcf6600, 0x0000000100000000)
 Metaspace       used 2637K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 283K, capacity 386K, committed 512K, reserved 1048576K

我們應該能夠識別簡單 GC 日誌中的所有元素。但有幾個新的點。

現在,讓我們考慮輸出中以藍色突出顯示的新項:

5.1 分析年輕代裏的Minor GC

  • [GC (Allocation Failure) [DefNew: 279616K->34944K(314560K), 0.3626923 secs] 279616K->146232K(1013632K), 0.3627492 secs] [Times: user=0.33 sys=0.03, real=0.36 secs]

我們拆分一下:

  1. GC日誌開頭的“[GC”和“[Full GC”說明了這次垃圾收集的停頓類型,而不是用來區分新生代GC還是老年代GC的。如果有“Full”,說明這次GC是發生了Stop-The-World的。
  2. DefNew - 垃圾回收使用的名字,這是一個單線程的收集器,發生了 mark-copy stop-the-world ,清理新生代使用的空間。
  3. 279616K->34944K – GC前該內存區域已使用容量-> GC後該內存區域已使用容量 (該內存區域總容量)
  4. 0.3626923 secs – 表示該內存區域GC所佔用的時間,單位是秒
  5. [Times: user=0.33 sys=0.03, real=0.36 secs] – GC 持續的時間,從不同維度統計的。

介紹一下:

  • user – 進程執行用戶態代碼(核心之外)所使用的時間。這是執行此進程所使用的實際 CPU 時間,其他進程和此進程阻塞的時間並不包括在內。在垃圾收集的情況下,表示 GC 線程執行所使用的 CPU 總時間。
  • sys – 進程在內核態消耗的 CPU 時間,即在內核執行系統調用或等待系統事件所使用的 CPU 時間。
  • real – 程序從開始到結束所用的時鐘時間。這個時間包括其他進程使用的時間片和進程阻塞的時間(比如等待 I/O 完成)。

5.2 分析 Full GC

在這個倒數第二個示例中,我們看到,對於由系統調用觸發的Full GC,使用的收集器是 Tenured。

我們看到的最後一條信息是元空間的相同模式的細分:

[Metaspace: 2624K->2624K(1056768K)], 0.5580738 secs]

Metaspace - JDK 8.HotSpot JVM開始使用本地化的內存存放類的元數據,這個空間叫做元空間

輸出的最後一部分包括堆的細目,包括內存每個部分的內存佔用量摘要。

Eden去佔用了 35%,Tenured佔用了52%. 元數據空間和類空間也被包括了。

從上面的輸出,我們能清楚的知道,在JVM內部,GC事件消耗了多少內存。

6.增加日期和時間

沒有日期和時間信息,任何好的日誌都是不完整的。

當我們需要將 GC 日誌數據與其他源的數據關聯時,這些額外信息非常有用,或者它只能幫助促進搜索。

當我們運行應用程序以獲取顯示日誌的日期和時間信息時,我們可以添加以下兩個參數:

-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps

現在,每行都以絕對日期和時間開始,寫入時跟一個時間戳,反映自 JVM 啓動以來以秒爲單位的實時傳遞時間:

2018-12-11T02:55:23.518+0100: 2.601: [GC (Allocation ...

7.存入文件

默認的日誌輸出是控制檯,我們可以存入文件:

我們可以添加參數-Xloggc:<file>

-Xloggc:/path/to/file/gc.log

8.Java 9以上新增的功能

在GC日誌過程中,會遇到這麼個配置:-XX:+PrintGC。在Java8裏面,-verbose:gcXX:+PrintGC是一樣的。

-XX:+PrintGC在Java9被棄用了。-Xlog:gc在JDK9和10中仍然可以使用。

9.分析GC的工具

使用文本編輯器分析 GC 日誌可能非常耗時且非常繁瑣。根據使用的 JVM 版本和 GC 算法,GC 日誌格式可能會有所不同。

有一個非常好的免費圖形分析工具,用於分析垃圾回收日誌,提供有關潛在垃圾回收問題的許多指標,甚至爲這些問題提供潛在的解決方案。

請下載 Universal GC Log Analyzer

參考代碼

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