1. 程式人生 > >【譯】Java SE 14 Hotspot 虛擬機器垃圾回收調優指南

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

原文連結:[HotSpot Virtual Machine Garbage Collection Tuning Guide](https://docs.oracle.com/en/java/javase/14/gctuning/introduction-garbage-collection-tuning.html),基於Java SE 14。 本文主要包括以下內容: - [優化目標與策略(Ergonomics)](#優化目標與策略ergonomics) - [垃圾收集器實現(Garbage Collector Implementation)](#垃圾收集器實現garbage-collector-implementation) - [影響垃圾收集效能的因素](#影響垃圾收集效能的因素) - [總堆(Total Heap)](#總堆total-heap) - [年輕代](#年輕代) - [可用的收集器(Available Collectors)](#可用的收集器available-collectors) - [序列收集器(Serial Collector)](#序列收集器serial-collector) - [並行收集器(Parallel Collector)](#並行收集器parallel-collector) - [G1收集器(Garbage-First Garbage Collector)](#g1收集器garbage-first-garbage-collector) - [Z收集器(The Z Garbage Collector)](#z收集器the-z-garbage-collector) - [選擇收集器](#選擇收集器) - [並行收集器](#並行收集器) - [G1垃圾收集器](#g1垃圾收集器) - [啟用G1](#啟用g1) - [基本概念](#基本概念) - [G1內部細節](#g1內部細節) - [G1 GC的預設選項](#g1-gc的預設選項) - [與其它收集器的比較](#與其它收集器的比較) - [Z垃圾收集器](#z垃圾收集器) - [其它考慮因素](#其它考慮因素) - [顯式垃圾回收](#顯式垃圾回收) - [類元資料(Class Metadata)](#類元資料class-metadata) # 優化目標與策略(Ergonomics) ## 垃圾收集器、堆和執行時編譯器預設選擇 - G1(Garbage First)收集器 - GC執行緒的最大值受限於堆大小和可用的CPU資源 - 初始堆空間為實體記憶體的1/64 - 最大堆空間為實體記憶體的1/4 - 分層編譯器,同時使用C1和C2 可以將 Java HotSpot VM 垃圾收集器配置為優先滿足兩個目標之一:最大暫停時間和應用吞吐量。 如果首選目標得到滿足,收集器將嘗試最大化其他目標。 ## 最大暫停時間目標(Maximum Pause-Time Goal) 暫停時間是垃圾收集器停止應用程式並恢復不再使用的空間的持續時間。 最大暫停時間目標的意圖是限制這些暫停的最長時間。 使用命令列選項 ```-XX:MaxGCPauseMillis=``` 指定最大暫停時間目標。這被解釋為向垃圾回收器提示,需要的暫停時間為 nnn 毫秒或更短。 垃圾收集器調整 Java 堆大小和其他與垃圾收集相關的引數,以使垃圾收集暫停時間小於 nnn 毫秒。 最大暫停時間目標的預設值隨收集器的不同而變化。 這些調整可能會導致垃圾收集更頻繁地發生,從而降低應用程式的總吞吐量。 但是,在某些情況下,暫停時間的預期目標無法實現。 ## 吞吐量目標(Throughput Goal) 吞吐量目標是根據收集垃圾所花費的時間來度量的,而垃圾收集之外所花費的時間是應用程式時間。 目標由命令列選項 ```-XX:GCTimeRatio=nnn``` 指定。垃圾收集時間與應用程式時間的比值為 ```1/ (1+nnn)```。 例如, ```-XX:GCTimeRatio=19``` 設定了垃圾收集總時間的 1/20 或 5% 的目標。 用於垃圾收集的時間是所有垃圾收集引起的暫停的總時間。如果吞吐量目標沒有達到,那麼垃圾收集器可能採取的一個行動是增加堆的大小,以便應用程式在收集暫停之間花費的時間可以更長。 ## 使用空間(Footprint) 如果吞吐量和最大停頓時間目標已經達到,那麼垃圾收集器就會減少堆的大小,直到其中一個目標(總是吞吐量目標)無法達到為止。垃圾收集器可以使用的最小和最大堆大小可以分別使用 ```-Xms=``` 和 ```-Xmx=``` 來設定最小和最大堆大小。 # 垃圾收集器實現(Garbage Collector Implementation) ## 分代垃圾收集(Generational Garbage Collection) 一個物件被認為是垃圾,當無法從正在執行的程式中的任何其他活躍物件的引用訪問到它時,VM 可以重用它的記憶體。 理論上,最簡單的垃圾收集演算法在每次執行時遍歷每個可達物件。任何剩下的東西都被認為是垃圾。這種方法花費的時間與活躍物件的數量成正比,這對於維護大量活躍資料的大型應用程式來說是禁止的。 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,分別為36、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=``` 和 ```-XX:MaxHeapFreeRatio=``` 設定為百分比,總大小限制在 ```–Xms``` 和 ```–Xmx```之間。 使用這些選項,如果一代中的可用空間比例低於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=``` 控制垃圾收集器執行緒的數量。 ## 並行收集器中分代的排列 在並行收集器中,各代的排列方式是不同的。 **圖6-1 並行收集器中各代的排列** ## 並行收集器調優(Parallel Collector Ergonomics) 當使用 ```-XX:+UseParallelGC``` 選擇並行收集器時,它支援自動調優方法,允許您指定行為,而不是分代大小和其他低階調優細節。 ### 指定並行收集器行為的選項 - 最大垃圾收集暫停時間: 使用命令列選項 ```-XX:MaxGCPauseMillis=``` 指定最大暫停時間目標。這被解釋為需要 毫秒或更少的暫停時間;預設情況下,沒有最大暫停時間目標。如果指定了暫停時間目標,則會調整堆大小和與垃圾收集有關的其他引數,以使垃圾收集暫停時間短於指定值; 但是,可能並不總是能夠達到所需的暫停時間目標。 這些調整可能會導致垃圾收集器降低應用程式的總吞吐量。 - 吞吐量: 吞吐量目標是根據執行垃圾回收所花費的時間與垃圾回收之外所花費的時間(稱為應用程式時間)來度量的。目標由命令列選項 ```-XX:GCTimeRatio=``` 指定,該選項將垃圾收集時間與應用程式時間的比率設定為1 / (1 + )。 例如, ```-XX:GCTimeRatio=19``` 設定了垃圾收集佔總時間的1/20或5%的目標。 預設值為99,結果是垃圾回收時間的目標為1%。 - 記憶體空間: 使用選項 ```-Xmx``` 指定最大堆記憶體佔用。此外,收集器還有一個隱式目標,即在滿足其他目標的情況下最小化堆的大小。 ### 並行收集器目標的優先順序 目標是最大暫停時間目標、吞吐量目標和最小佔用空間目標,目標按照這個順序實現: 首先實現最大暫停時間目標。只有在滿足了這個要求之後,吞吐量目標才能實現。 同樣,只有在前兩個目標已經實現之後,才會考慮記憶體大小目標。 ### 並行收集器預設堆大小 除非在命令列中指定了初始堆大小和最大堆大小,否則將根據計算機上的記憶體量計算它們。預設的最大堆大小是實體記憶體的1/4,而初始堆大小是實體記憶體的1/64。 分配給年輕代的最大空間是總堆大小的1/3。 #### 並行收集器初始和最大堆大小的規範 你可以使用選項 ```-Xms```和 ```-Xmx``` 指定初始堆大小和最大堆大小。 如果您知道應用程式需要多少堆才能正常工作,那麼可以將 ```-Xms``` 和 ```-Xmx``` 設定為相同的值。如果您不知道,那麼 JVM 將開始使用初始堆大小,然後增加 Java 堆,直到找到堆使用量和效能之間的平衡。 其他引數和選項可能會影響這些預設值。要驗證預設值,請使用 ```-XX:+PrintFlagsFinal``` 選項並在輸出中查詢 ```-XX:MaxHeapSize```。 例如,在 Linux 上你可以執行以下命令: ```bash java -XX:+PrintFlagsFinal -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``` 對用於類元資料的本機記憶體量設定