【譯】Java SE 14 Hotspot 虛擬機垃圾回收調優指南

原文連接:HotSpot Virtual Machine Garbage Collection Tuning Guide,基於Java SE 14。html

本文主要包括如下內容:java

優化目標與策略(Ergonomics)

垃圾收集器、堆和運行時編譯器默認選擇

  • G1(Garbage First)收集器
  • GC線程的最大值受限於堆大小和可用的CPU資源
  • 初始堆空間爲物理內存的1/64
  • 最大堆空間爲物理內存的1/4
  • 分層編譯器,同時使用C1和C2

能夠將 Java HotSpot VM 垃圾收集器配置爲優先知足兩個目標之一:最大暫停時間和應用吞吐量。 若是首選目標獲得知足,收集器將嘗試最大化其餘目標。web

最大暫停時間目標(Maximum Pause-Time Goal)

暫停時間是垃圾收集器中止應用程序並恢復再也不使用的空間的持續時間。 最大暫停時間目標的意圖是限制這些暫停的最長時間。算法

使用命令行選項 -XX:MaxGCPauseMillis=<nnn> 指定最大暫停時間目標。這被解釋爲向垃圾回收器提示,須要的暫停時間爲 nnn 毫秒或更短。 垃圾收集器調整 Java 堆大小和其餘與垃圾收集相關的參數,以使垃圾收集暫停時間小於 nnn 毫秒。 最大暫停時間目標的缺省值隨收集器的不一樣而變化。 這些調整可能會致使垃圾收集更頻繁地發生,從而下降應用程序的總吞吐量。 可是,在某些狀況下,暫停時間的預期目標沒法實現。bash

吞吐量目標(Throughput Goal)

吞吐量目標是根據收集垃圾所花費的時間來度量的,而垃圾收集以外所花費的時間是應用程序時間。服務器

目標由命令行選項 -XX:GCTimeRatio=nnn 指定。垃圾收集時間與應用程序時間的比值爲 1/ (1+nnn)。 例如, -XX:GCTimeRatio=19 設置了垃圾收集總時間的 1/20 或 5% 的目標。數據結構

用於垃圾收集的時間是全部垃圾收集引發的暫停的總時間。若是吞吐量目標沒有達到,那麼垃圾收集器可能採起的一個行動是增長堆的大小,以便應用程序在收集暫停之間花費的時間能夠更長。多線程

使用空間(Footprint)

若是吞吐量和最大停頓時間目標已經達到,那麼垃圾收集器就會減小堆的大小,直到其中一個目標(老是吞吐量目標)沒法達到爲止。垃圾收集器可使用的最小和最大堆大小能夠分別使用 -Xms=<nnn>-Xmx=<mmm> 來設置最小和最大堆大小。併發

垃圾收集器實現(Garbage Collector Implementation)

分代垃圾收集(Generational Garbage Collection)

一個對象被認爲是垃圾,當沒法從正在運行的程序中的任何其餘活躍對象的引用訪問到它時,VM 能夠重用它的內存。oracle

理論上,最簡單的垃圾收集算法在每次運行時遍歷每一個可達對象。任何剩下的東西都被認爲是垃圾。這種方法花費的時間與活躍對象的數量成正比,這對於維護大量活躍數據的大型應用程序來講是禁止的。

Java HotSpot 虛擬機合併了許多不一樣的垃圾收集算法,除了 ZGC 以外,這些算法都使用一種稱爲分代收集的技術。雖然簡單的垃圾收集每次都會檢查堆中的每一個活動對象,但分代收集利用了大多數應用程序的一些經驗觀察到的屬性,以最小化回收未使用(垃圾)對象所需的工做。這些被觀察到的性質中最重要的是弱世代假說,即大多數對象只能存活很短的時間。

圖3-1中的藍色區域是對象生命週期的典型分佈。X軸顯示以分配的字節爲單位的對象生存時間。Y 軸上的字節數是對象中具備相應生存期的總字節數。左邊的尖峯表示能夠回收的對象(換句話說,已經「死亡」)。 例如,迭代器對象一般只在單個循環期間保持活動。

圖3-1 對象生命週期的典型分佈

有些對象確實存活時間更長,所以分佈向右延伸。例如,一般有一些在初始化時分配的對象會一直存在直到 VM 退出。介於這兩個極端之間的是在某些中間計算期間存活的對象,這裏看到的是初始峯值右側的塊。有些應用程序具備很是不一樣的外觀分佈,但使人驚訝的是,大量應用程序具備這種通常形狀。經過關注大多數對象「早逝」這一事實,高效的收集成爲可能。

世代(Generations)

爲了對此場景進行優化,對內存進行分代管理(存放不一樣年齡段對象的內存池)。垃圾回收在每一代填滿時發生。

絕大多數對象分配在一個專門用於年輕對象的池中(年輕代) ,大多數對象死在那裏。 當年輕代的垃圾填滿時,觸發minor回收,只有年輕代的垃圾會被回收,而其餘代的垃圾則不會被回收。這種收集的成本,在第一階段,與被收集的活對象數量成正比; 年輕代回收垃圾很是快。一般,在每次minor回收期間,年輕代倖存的對象中的一部分被移動到老年代。最終,老年代將被填滿而且必須被回收,從而形成major回收,在這個回收中將收集整個堆。major回收一般比minor集合持續時間長得多,由於涉及的對象數量要大得多。圖3-2 顯示了串行垃圾收集器中代的默認安排:

圖3-2 串行收集器中各代的默認安排

在啓動時,Java HotSpot VM將整個Java堆保留在地址空間中,但除非須要,不然不爲其分配任何物理內存。覆蓋 Java 堆的整個地址空間在邏輯上被劃分爲年輕代和老年代。保留給對象存儲的完整地址空間能夠分爲年輕代和老年代。

年輕代由伊甸園(eden)和兩個倖存者(survivor)空間組成。大多數對象最初是在伊甸園中分配的。一個倖存者空間在任什麼時候候都是空的,而且在垃圾收集過程當中做爲伊甸園和另外一個倖存者空間中活動對象的目的地; 在垃圾回收以後,伊甸園和源倖存者空間都是空的。 在下一次垃圾收集中,將交換兩個倖存者空間的用途。最近填充的一個空間是將活動對象複製到其餘倖存者空間的源。對象以這種方式在倖存者空間之間複製,直到它們被複制了必定次數,或者那裏沒有足夠的空間。這些對象被複制到老年區域中。這個過程也被稱爲衰老。

性能考慮因素

垃圾收集的主要度量指標是吞吐量和延遲。

  • 吞吐量是在長時間內沒有花在垃圾收集總時間的百分比。吞吐量包括分配所花費的時間(但一般不須要對分配速度進行調優)。
  • 延遲是應用程序的響應能力。垃圾收集暫停會影響應用程序的響應能力。

用戶對垃圾回收有不一樣的要求。

吞吐量和佔用空間測量(Throughput and Footprint Measurement)

吞吐量和佔用空間最好使用特定於應用程序的指標來度量。

例如,web 服務器的吞吐量可使用一個客戶端負載生成器進行測試。 可是,經過檢查虛擬機自己的診斷輸出,很容易估計垃圾收集引發的暫停。命令行選項 -verbose:gc 打印有關堆和垃圾收集的信息。下面是一個例子:

[15,651s][info ][gc] GC(36) Pause Young (G1 Evacuation Pause) 239M->57M(307M) (15,646s, 15,651s) 5,048ms
[16,162s][info ][gc] GC(37) Pause Young (G1 Evacuation Pause) 238M->57M(307M) (16,146s, 16,162s) 16,565ms
[16,367s][info ][gc] GC(38) Pause Full (System.gc()) 69M->31M(104M) (16,202s, 16,367s) 164,581ms

輸出顯示了兩個年輕代的回收,接着是應用程序經過調用 System.gc() 啓動的full回收。這些行以一個時間戳開始,表示從應用程序啓動時開始的時間。 接下來是關於這一行的日誌級別(info)和標記(gc)的信息。 而後是 GC 標識號。 在本例中,有三個 gc,分別爲3六、37和38。 而後記錄 GC 的類型和聲明 GC 的緣由。 在此以後,將記錄有關內存消耗的一些信息。該日誌使用的格式:「GC以前使用的堆空間」 -> 「GC後使用的堆空間」。

示例的第一行是239M->57M(307M),這意味着在GC前使用239MB,而且GC清除了大部份內存,可是57 MB保留了下來。堆大小爲307 MB。注意,在這個示例中,full GC 將堆從307 MB 縮小到104 MB。在內存使用信息以後,記錄 GC 的開始和結束時間以及持續時間(end-start)。

-verbose:gc 命令是 -Xlog:gc 的別名。-Xlog 是 HotSpot JVM 中日誌記錄的通用日誌記錄配置選項。 這是一個基於標記的系統,其中 gc 是標記之一。要得到有關 GC 正在作什麼的更多信息,能夠配置日誌記錄,以打印包含 GC 標記和任何其餘標記的任何消息。此選項的命令行選項是-Xlog:gc*

下面是一個用-Xlog:gc* 記錄的 G1 年輕代回收的示例:

[10.178s][info][gc,start ] GC(36) Pause Young (G1 Evacuation Pause) 
[10.178s][info][gc,task ] GC(36) Using 28 workers of 28 for evacuation 
[10.191s][info][gc,phases ] GC(36) Pre Evacuate Collection Set: 0.0ms
[10.191s][info][gc,phases ] GC(36) Evacuate Collection Set: 6.9ms 
[10.191s][info][gc,phases ] GC(36) Post Evacuate Collection Set: 5.9ms 
[10.191s][info][gc,phases ] GC(36) Other: 0.2ms 
[10.191s][info][gc,heap ] GC(36) Eden regions: 286->0(276) 
[10.191s][info][gc,heap ] GC(36) Survivor regions: 15->26(38)
[10.191s][info][gc,heap ] GC(36) Old regions: 88->88 
[10.191s][info][gc,heap ] GC(36) Humongous regions: 3->1 
[10.191s][info][gc,metaspace ] GC(36) Metaspace: 8152K->8152K(1056768K)
[10.191s][info][gc ] GC(36) Pause Young (G1 Evacuation Pause) 391M->114M(508M) 13.075ms 
[10.191s][info][gc,cpu ] GC(36) User=0.20s Sys=0.00s Real=0.01s

影響垃圾收集性能的因素

影響垃圾收集性能的兩個最重要的因素是總可用內存專用於年輕代的堆的比例

總堆(Total Heap)

影響垃圾收集性能的最重要因素是總可用內存。 因爲收集發生在代填滿時,所以吞吐量與可用內存量成反比。

影響生成代大小的堆選項

許多選項影響代大小。圖4-1說明了堆中提交的空間和虛擬空間之間的區別。在初始化虛擬機時,將保留堆的整個空間。可使用 -Xmx 選項指定保留空間的大小。 若是 -Xms 參數的值小於 -Xmx 參數的值,那麼並不是全部保留的空間都當即提交給虛擬機。未提交的空間在這個圖中被標記爲「virtual」。堆的不一樣部分,即老年代和年輕代,能夠根據須要增加到虛擬空間的極限。

其中一些參數是堆的一部分與另外一部分的比率。例如,參數 –XX:NewRatio 表示老年代與年輕代的相對大小。

圖4-1 堆選項

堆大小的默認選項值

默認狀況下,虛擬機在每次回收中增長或縮小堆,以便將每次回收中的可用空間與活動對象的比例保持在特定範圍內。

此目標範圍由選項 -XX:MinHeapFreeRatio=<minimum>-XX:MaxHeapFreeRatio=<maximum> 設置爲百分比,總大小限制在 –Xms<min>–Xmx<max>之間。

使用這些選項,若是一代中的可用空間比例低於40% ,那麼這一代將擴展到保持40% 的可用空間,直到這一代的最大容許空間大小。相似地,若是可用空間超過70% ,那麼這一代就會收縮,以便只有70% 的空間是可用的,這取決於這一代的最小大小。

Java SE 中用於並行收集器的計算如今用於全部的垃圾收集器。計算的一部分是64位平臺的最大堆大小的上限。對於客戶端JVM也有相似的計算,這會致使堆的最大空間小於服務器JVM。

如下是關於服務器應用程序堆大小的通常準則:

  • 除非你有暫停問題,不然請嘗試向虛擬機授予儘量多的內存。 默認大小一般過小。
  • -Xms-Xmx設置爲相同的值能夠從虛擬機中刪除最重要的大小調整決策,從而提升可預測性。可是,若是你作了一個糟糕的選擇,那麼虛擬機就沒法進行補償。
  • 一般,隨着處理器數量的增長而增長內存,由於分配能夠並行進行。

經過最小化 Java 堆大小來節約動態內存佔用

若是你須要最小化應用程序的動態內存佔用(執行過程當中消耗的最大 RAM) ,那麼能夠經過最小化 Java 堆大小來實現這一點。

使用命令行選項-XX:MaxHeapFreeRatio(默認值爲70%) 和 -XX:MinHeapFreeRatio (默認值爲40%)下降相關比例,從而最小化 Java 堆大小。

年輕代

除了總的可用內存以外,影響垃圾收集性能的第二個最重要的因素是專用於年輕代的堆的比例。

年輕代規模的選擇

默認狀況下,年輕代的大小由選項 -XX:NewRatio 控制。

例如,設置 -XX:NewRatio=3 意味着年輕代和老年代之間的比例爲1:3。 換句話說,伊甸園 和 倖存者空間的總和將是堆總大小的四分之一。

選項 -XX:NewSize-XX:MaxNewSize設置了年輕代的下限和上限。 將這些值設置爲相同的值能夠固定年輕代,就像將 -Xms-Xmx 設置爲相同的值能夠固定堆總大小同樣。這有助於以比 -XX:NewRatio 所容許的整數倍更細的粒度調優年輕代。

倖存者空間調整

你可使用選項 -XX:SurvivorRatio 來調整倖存者空間的大小,但這一般對性能並不重要。

例如, -XX:SurvivorRatio=6 將伊甸園和倖存者空間之間的比率設置爲1:6。 換句話說,每一個倖存者的空間是伊甸園的1/6,也就是年輕代的1/8(不是1/7,由於存在兩個倖存者的空間)。

若是倖存者空間過小,那麼複製收集將直接溢出到老年代中。若是倖存者空間太大,那麼它們就是無用的空。在每次垃圾收集時,虛擬機都會選擇一個閾值數字,這是一個對象在老化以前能夠複製的次數。選擇這個門檻是爲了讓倖存者保持半滿狀態。 你可使用日誌配置 -Xlog:gc,age可用於顯示此閾值以及新生成的對象的年齡。這對於觀察應用程序的生命週期分佈也頗有用。

表4-1 提供了倖存者空間大小的默認值。

選項 默認值
-XX:NewRatio 2
-XX:NewSize 1310 MB
-XX:MaxNewSize not limited
-XX:SurvivorRatio 8

年輕代的最大空間是根據總堆的最大空間和 -XX:NewRatio 參數的值計算出來的。-XX:MaxNewSize 參數的默認值"not limited" 意味着計算值不受 -XX:MaxNewSize 的限制,除非在命令行上指定了 -XX:MaxNewSize 的值。

可用的收集器(Available Collectors)

Java HotSpot虛擬機包含3種不一樣類型的收集器,每種收集器具備不一樣的性能特徵。

  • 串行收集器(Serial Collector)
  • 並行收集器(Parallel Collector)
  • G1收集器(Garbage-First Garbage Collector)

串行收集器(Serial Collector)

串行收集器使用單個線程執行全部垃圾收集工做,這使得它相對高效,由於線程之間沒有通訊開銷。

它最適合於單處理器機器,由於它不能利用多處理器硬件,儘管它能夠在多處理器上用於具備小數據集(大約100MB)的應用程序。在某些硬件和操做系統配置上,串行收集器是默認選擇的,或者可使用選項 -XX:+UseSerialGC 顯式啓用串行收集器。

並行收集器(Parallel Collector)

並行收集器也稱爲吞吐量收集器,它是一個相似於串行收集器的分代收集器。 串行和並行收集器之間的主要區別是,並行收集器有多個線程,用於加速垃圾收集。

並行收集器用於在多處理器或多線程硬件上運行的具備中等到大型數據集的應用程序。 您可使用 -XX:+UseParallelGC 選項啓用它。

並行壓縮是使並行收集器可以並行執行major回收的一個特性。若是不進行並行壓縮,major回收將使用單個線程執行,這將極大地限制可伸縮性。若是指定了 -XX:+UseParallelGC 選項,則默認狀況下啓用並行壓縮。 您可使用 -XX:-UseParallelOldGC 選項禁用它

G1收集器(Garbage-First Garbage Collector)

G1主要是一個併發收集器。大多數併發收集器併發執行一些代價高昂的工做到應用程序。 此收集器設計用於從小型機器擴展到大型具備大量內存的多處理器機器。 它提供了以高几率知足停頓時間目標的能力,同時實現高吞吐量。

在大多數硬件和操做系統配置中,默認選擇 G1,或者可使用 -XX:+UseG1GC 顯式啓用 G1。

Z收集器(The Z Garbage Collector)

Z垃圾收集器(ZGC)是一個可伸縮的低延遲垃圾收集器。ZGC併發地執行全部昂貴的工做,而不中止應用程序線程的執行。

ZGC 適用於須要低延遲(少於10毫秒的暫停) 或 使用很是大的堆(TB級)的應用程序。 能夠經過使用 -XX:+UseZGC 選項啓用。

ZGC是一個實驗性的特性,從 JDK 11開始。

選擇收集器

若是須要,調整堆大小以提升性能。若是性能仍然不能達到你的目標,那麼使用下面的準則做爲選擇收集器的起點:

  • 若是應用程序有一個小的數據集(大約100 MB) ,那麼使用選項 -XX:+UseSerialGC 選擇串行收集器。
  • 若是應用程序將在單處理器上運行,而且沒有暫停時間要求,那麼使用選項 -XX:+UseSerialGC 選擇串行收集器。
  • 若是(a)峯值應用程序性能是第一優先級,而且(b)沒有暫停時間要求或者一秒或更長的暫停是能夠接受的,那麼讓 虛擬機 選擇收集器或者用 -XX:+UseParallelGC選擇並行收集器。
  • 若是響應時間比總吞吐量更重要,而且垃圾收集暫停時間必須更短,那麼選擇主要併發的收集器,使用 -XX:+UseG1GC
  • 若是響應時間是一個高優先級,或者你正在使用一個很是大的堆,那麼選擇一個徹底併發的收集器,使用 -XX:UseZGC

這些準則只是選擇收集器的起點,由於性能取決於堆的大小、應用程序維護的實時數據量以及可用處理器的數量和速度。

若是推薦的收集器沒有達到預期的性能,那麼首先嚐試調整堆和分代大小,以知足預期的目標。 若是性能仍然不足,那麼嘗試另外一個收集器: 使用併發收集器來減小暫停時間,並使用並行收集器來增長多處理器硬件上的總吞吐量。

小結:

  • 若是應用程序是小數據集或是單處理器上運行,選擇串行收集器。
  • 若是吞吐量是第一優先級,而沒有暫停時間要求,選擇並行收集器。
  • 若是響應時間比吞吐量更重要,選擇G1收集器。
  • 若是最關注響應時間,或者堆很是大(TB級),則使用Z收集器。

並行收集器

並行收集器(也稱爲吞吐量收集器)是相似於串行收集器的分代收集器。 串行和並行收集器之間的主要區別是,並行收集器有多個線程,用於加速垃圾回收。

經過命令行選項 -XX:+UseParallelGC 啓用並行收集器。 默認狀況下,使用此選項,次要(minor)和主要(major)回收都將並行運行,以進一步減小垃圾回收開銷。

並行垃圾收集器線程數

可使用命令行選項 -XX:ParallelGCThreads=<N> 控制垃圾收集器線程的數量。

並行收集器中分代的排列

在並行收集器中,各代的排列方式是不一樣的。

圖6-1 並行收集器中各代的排列

並行收集器調優(Parallel Collector Ergonomics)

當使用 -XX:+UseParallelGC 選擇並行收集器時,它支持自動調優方法,容許您指定行爲,而不是分代大小和其餘低級調優細節。

指定並行收集器行爲的選項

  • 最大垃圾收集暫停時間: 使用命令行選項 -XX:MaxGCPauseMillis=<N> 指定最大暫停時間目標。這被解釋爲須要 毫秒或更少的暫停時間;默認狀況下,沒有最大暫停時間目標。若是指定了暫停時間目標,則會調整堆大小和與垃圾收集有關的其餘參數,以使垃圾收集暫停時間短於指定值; 可是,可能並不老是可以達到所需的暫停時間目標。 這些調整可能會致使垃圾收集器下降應用程序的總吞吐量。

  • 吞吐量: 吞吐量目標是根據執行垃圾回收所花費的時間與垃圾回收以外所花費的時間(稱爲應用程序時間)來度量的。目標由命令行選項 -XX:GCTimeRatio=<N> 指定,該選項將垃圾收集時間與應用程序時間的比率設置爲1 / (1 + )。

    例如, -XX:GCTimeRatio=19 設置了垃圾收集佔總時間的1/20或5%的目標。 默認值爲99,結果是垃圾回收時間的目標爲1%。

  • 內存空間: 使用選項 -Xmx<N> 指定最大堆內存佔用。此外,收集器還有一個隱式目標,即在知足其餘目標的狀況下最小化堆的大小。

並行收集器目標的優先級

目標是最大暫停時間目標、吞吐量目標和最小佔用空間目標,目標按照這個順序實現:

首先實現最大暫停時間目標。只有在知足了這個要求以後,吞吐量目標才能實現。 一樣,只有在前兩個目標已經實現以後,纔會考慮內存大小目標。

並行收集器默認堆大小

除非在命令行中指定了初始堆大小和最大堆大小,不然將根據計算機上的內存量計算它們。默認的最大堆大小是物理內存的1/4,而初始堆大小是物理內存的1/64。 分配給年輕代的最大空間是總堆大小的1/3。

並行收集器初始和最大堆大小的規範

你可使用選項 -Xms-Xmx 指定初始堆大小和最大堆大小。

若是您知道應用程序須要多少堆才能正常工做,那麼能夠將 -Xms-Xmx 設置爲相同的值。若是您不知道,那麼 JVM 將開始使用初始堆大小,而後增長 Java 堆,直到找到堆使用量和性能之間的平衡。

其餘參數和選項可能會影響這些默認值。要驗證默認值,請使用 -XX:+PrintFlagsFinal 選項並在輸出中查找 -XX:MaxHeapSize。 例如,在 Linux 上你能夠運行如下命令:

java -XX:+PrintFlagsFinal <GC options> -version | grep MaxHeapSize

過長的並行收集器時間和OutOfMemoryError

若是在垃圾回收(GC)上花費了太多時間,並行收集器將拋出 OutOfMemoryError 錯誤。

若是超過98% 的總時間用於垃圾回收,而回收的堆不到2%,則拋出 OutOfMemoryError。此特性旨在防止應用程序在較長時間內運行,同時因爲堆過小而幾乎或根本沒有進展。若是須要,能夠經過向命令行添加選項 -XX:-UseGCOverheadLimit 來禁用此特性。

G1垃圾收集器

G1垃圾收集器的目標是將多處理器機器擴展到大量內存。它試圖以較高的機率知足垃圾收集暫停時間目標,同時實現較高的吞吐量而不須要進行配置。G1的目標是使用當前的目標應用程序和環境,在延遲和吞吐量之間提供最佳的平衡。

與吞吐量收集器相比,雖然G1收集器的垃圾收集暫停時間一般要短得多,但應用程序吞吐量也每每略低。

G1是默認收集器。

啓用G1

G1垃圾回收器是默認回收器,所以一般不須要執行任何其餘操做。您能夠經過在命令行上提供 -XX:+UseG1GC 來顯式啓用它。

基本概念

G1是一個分代的、遞增的、並行的、大部分併發的、stop-the-world和疏散垃圾收集器,它監視每一個stop-the-world暫停的時間目標。與其餘收集器相似,G1將堆分爲(虛擬的)年輕代和老年代。空間回收的努力集中在年輕代身上,這樣作效率最高,偶爾的空間回收在老年代中。

有些操做老是在stop-the-world暫停中執行,以提升吞吐量。應用程序中止的其餘操做會花費更多時間,好比全局標記之類的整堆操做會與應用程序並行執行。 爲了使stop-the-world在空間回收方面的停頓時間縮短,G1逐步並行地進行空間回收。 G1經過跟蹤之前應用程序行爲的信息和垃圾收集暫停來構建相關成本的模型,從而實現可預測性。它利用這個信息來計算停頓時所作的工做量。例如,G1首先在效率最高的區域回收空間(這些區域大部分都是垃圾,所以取名爲 G1)。

G1主要經過撤離來回收空間: 在選定的內存區域內找到的活動對象被複制到新的內存區域,並在處理過程當中對其進行壓縮。在完成疏散以後,之前被活動對象佔用的空間將被應用程序重用以進行分配。

G1收集器不是實時收集器。它試圖在更長的時間內以高几率實現設定的暫停時間目標,但在給定的暫停時間內並不老是絕對肯定。

堆佈局

G1將堆劃分爲一組大小相同的堆區域,每一個區域都有一個連續的虛擬內存範圍,如圖7-1所示。區域是內存分配和內存回收的單位。在任何給定的時間,這些區域中的每個均可以是空的(淺灰色) ,或者分配給特定的一代,年輕的或老年的。當內存請求進入時,內存管理器分配空閒區域。內存管理器將它們分配給一個代,而後將它們做爲可用空間返回給應用程序,應用程序能夠將其分配給本身。

圖7-1 G1垃圾收集器堆佈局

年輕代包含伊甸園區域(紅色)和倖存者區域(紅色帶有"S")。這些區域提供了與其餘收集器中的相應連續空間相同的功能,不一樣之處在於,在G1中,這些區域一般以非連續的模式佈局在內存中。老區域(淺藍色)組成了老年代。對於跨越多個區域的對象,老年代區域可能很是巨大(淺藍色帶"H")。

應用程序老是分配給年輕代,即伊甸園區域,但直接分配給老年代的大型對象除外。

垃圾回收週期

在較高的水平上,G1收集器在兩個階段之間交替。只有年輕(young-only)階段包含垃圾回收,這些垃圾回收會逐漸用老年代中的對象填充當前可用的內存。在空間回收階段,除了處理年輕代的問題外,G1逐步收回老年代的空間。而後循環從新開始,只有年輕的階段。

Figure 9-2 gives an overview about this cycle with an example of the sequence of garbage collection pauses that could occur:

圖7-2給出了這個循環的概述,並舉例說明了可能發生的垃圾收集暫停的順序:

圖7-2 垃圾收集週期概覽

下面的列表詳細描述了G1垃圾收集週期的各個階段,它們之間的停頓和過渡:

  1. 純年輕(Young-only)階段: 這個階段從幾個普通(Normal)的年輕代回收開始,將對象升級到老年代。 當老年代佔有率達到必定閾值時,即初始堆佔有率閾值,純年輕(young-only)階段和空間回收(space-reclamation)階段開始轉換。此時,G1計劃一個併發啓動(Concurrent Start)年輕代回收,而不是普通(Normal)的年輕代回收。
    • 併發啓動(Concurrent Start):這種類型的回收除了執行普通年輕代回收以外,還啓動標記(marking)過程。
    • 重標記(Remark):此暫停將自行肯定標記,執行全局引用處理和類卸載,回收徹底空的區域並清理內部數據結構。
    • 清理(Cleanup):這個暫停決定了是否會真正進入空間回收階段。
  2. 空間回收(Space-reclamation)階段:這一階段包括多個混合(Mixed)回收,除了年輕代區域,還刪除老一代區域的成套活動對象。當G1認爲刪除更多的老年代區域不會產生足夠的自由空間時,空間回收階段就結束了。

在空間回收以後,收集週期從另外一個young-only的階段從新開始。做爲備份,若是應用程序在收集存活信息時耗盡了內存,G1會像其餘收集器同樣執行就地stop-the-world的徹底堆壓縮(Full GC)。

G1內部細節

Java堆大小調整

G1在調整Java堆大小時遵循標準規則,使用 -XX:InitialHeapSize 做爲最小的 Java 堆空間, -XX:MaxHeapSize 做爲最大的 Java 堆空間, -XX:MinHeapFreeRatio 做爲最小的可用內存百分比, -XX:MaxHeapFreeRatio 用於肯定調整大小後可用內存的最大百分比。 G1收集器僅在執行重標記(Remark) 和 Full GC 暫停期間考慮調整 Java 堆的大小。 這個過程能夠從操做系統釋放內存或分配內存。

Young-Only階段代調整

G1老是在下一個突變子階段的正常年輕代回收結束時測量年輕代的大小。經過這種方式,G1能夠知足使用 -XX:MaxGCPauseTimeMillis-XX:PauseTimeIntervalMillis 設置的暫停時間目標,該目標基於對實際暫停時間的長期觀察。它考慮到了一樣規模的年輕代須要多長時間才能刪除。這包括在回收過程當中須要複製多少對象以及這些對象之間的互聯程度等信息。

若是沒有其餘限制,那麼 G1能夠在 -XX:G1NewSizePercent-XX:G1MaxNewSizePercent 肯定的值之間自適應地調全年輕代大小,以知足暫停時間的要求。

或者,可使用 -XX:NewSize-XX:MaxNewSize 分別設置年輕代的最小值和最大值。

注意: 只指定後面這些選項中的一個,就能夠將年輕代大小精確地固定爲分別使用 -XX:NewSize-XX:MaxNewSize 傳遞的值。這將禁用暫停時間控制。

空間回收階段的代調整

在空間回收階段,G1試圖在一次垃圾回收暫停中最大化在老年代中回收的空間量。 年輕年代的大小設置爲容許的最小值,一般由 -XX:G1NewSizePercent 肯定。

週期性的垃圾收集

若是因爲應用程序不活躍而致使長時間沒有垃圾收集,那麼虛擬機可能會長時間保留大量未使用的內存,這些內存能夠在其餘地方使用。爲了不這種狀況,能夠強制 G1使用 -XX:G1PeriodicGCInterval 選項執行常規垃圾收集。此選項肯定 G1考慮執行垃圾回收的最小間隔(毫秒)。若是自之前任何垃圾收集暫停以來已通過去了這段時間,而且沒有正在進行的併發循環,G1將觸發額外的垃圾回收。

肯定初始堆佔用率

啓動堆佔用百分比(Initiating Heap Occupancy Percent, IHOP)是觸發初始標記回收的閾值,它被定義爲老年代大小的百分比。

默認狀況下,G1經過在標記週期中觀察標記須要多長時間以及在老年代中一般分配多少內存來自動肯定最佳IHOP。這個特性稱爲自適應IHOP。若是這個特性是活動的,那麼選項 -XX:InitiatingHeapOccupancyPercent 肯定初始值做爲當前老年代代大小的百分比,只要沒有足夠的觀測值來很好地預測啓動堆佔用閾值。 使用 -XX:-G1UseAdaptiveIHOP 選項關閉 G1的此行爲。 在這種狀況下, -XX:InitiatingHeapOccupancyPercent 的值老是決定這個閾值。

標記

G1標記使用一種稱爲「初始快照」(Snapshot-At-The-Beginning,SATB)的算法。 它在初始標記暫停時拍攝堆的虛擬快照,此時全部在標記開始時處於活動狀態的對象都被認爲在標記的剩餘時間處於活動狀態。這意味着,爲了空間回收的目的(除了一些例外) ,在標記期間變爲死的(不可到達的)對象仍然被認爲是活的。與其餘收集器相比,這可能會致使一些額外的內存被錯誤地保留。可是,SATB 可能在Remark暫停期間提供更好的延遲。在這個標記期間過於保守地考慮活動對象將在下一個標記期間被回收。

G1 GC的默認選項

選項和默認值
描述
-XX:MaxGCPauseMillis=200 最大暫停時間的目標
-XX:GCPauseTimeInterval= 最大暫停時間間隔的目標。 默認狀況下,G1不設置任何目標,容許 G1在極端狀況下背靠背地執行垃圾收集。
-XX:ParallelGCThreads= 垃圾回收暫停期間用於並行工做的最大線程數。 這是根據虛擬機如下列方式運行的計算機的可用線程數得出的: 若是進程可用的 CPU 線程數少於或等於8,則使用該線程。不然,使用線程數的5/8。
-XX:ConcGCThreads=
-XX:+G1UseAdaptiveIHOP
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1HeapRegionSize=
-XX:G1NewSizePercent=5
-XX:G1MaxNewSizePercent=60
-XX:G1HeapWastePercent=5
-XX:G1MixedGCCountTarget=8
-XX:G1MixedGCLiveThresholdPercent=85

與其它收集器的比較

這是G1與其餘收集器之間主要區別的摘要:

  • 並行 GC 只能做爲一個總體壓縮和回收老年代中的空間。G1增量地將這些工做分配到多個更短的回收中。這大大縮短了暫停時間,可是卻下降了吞吐量。
  • G1併發執行部分老年代空間回收。
  • G1可能比上述收集器顯示更高的開銷,因爲併發性而影響吞吐量。
  • ZGC針對很是大的堆,目的是以更高的吞吐量成本提供更小的停頓時間。

因爲它的工做原理,G1有一些獨特的機制來提升垃圾回收效率:

  • 在任何回收過程當中,G1均可以回收老年代中一些徹底空置的、大的區域。 這能夠避免許多其餘沒必要要的垃圾回收,不須要太多努力就能夠釋放大量空間
  • G1能夠選擇嘗試同時對Java堆上的重複字符串進行重複數據刪除。

從老年代回收空的大型對象始終處於啓用狀態。您可使用 -XX:-G1EagerReclaimHumongousObjects 選項禁用此功能。 默認狀況下禁用字符串重複數據刪除。 您可使用選項 -XX:+G1EnableStringDeduplication 啓用它。

Z垃圾收集器

Z垃圾收集器(ZGC)是一個可伸縮的低延遲垃圾收集器。ZGC併發地執行全部昂貴的工做,而不須要中止應用程序線程的執行超過10ms,這使得它適合於須要低延遲或使用很是大的堆(TB級)的應用程序。

Z垃圾收集器是一個實驗性特性,能夠經過命令行選項 -XX:+UnlockExperimentalVMOptions -XX:+UseZGC 啓用。

設置堆大小

ZGC最重要的調優選項是設置最大堆大小(-Xmx)。

設置併發GC線程數

可能須要考慮的第二個調優選項是設置併發GC線程的數量(-XX:ConcGCThreads)。

其它考慮因素

顯式垃圾回收

應用程序與垃圾回收交互的另外一種方式是使用 System.gc() 顯式調用full垃圾回收。

類元數據(Class Metadata)

Java類在 Java Hotspot虛擬機中有一個內部表示,稱爲類元數據。

在Java Hotspot虛擬機的之前版本中,類元數據是在所謂的永久代(permanent generation)中分配的。從JDK 8開始,永久代被刪除,類元數據在本機內存中(native memory)分配。默認狀況下,可用於類元數據的本機內存量是無限的。使用選項 -XX:MaxMetaspaceSize 對用於類元數據的本機內存量設置上限。

相關文章
相關標籤/搜索