Java 14 Hotspot 虛擬機垃圾回收調優指南!

Java技術棧

www.javastack.cn

打開網站看更多優質文章

作者:大鵬123

出處:www.cnblogs.com/sxpujs/p/12638114.html

優化目標與策略(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 表示老年代與年輕代的相對大小。

分享:46張PPT弄懂JVM、GC算法和性能調優!

圖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:NewRatio2
-XX:NewSize1310 MB
-XX:MaxNewSizenot limited
-XX:SurvivorRatio8

年輕代的最大空間是根據總堆的最大空間和 -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。

關注微信公衆號:Java技術棧,在後臺回覆:Java,可以獲取我整理的 N 篇 Java 及 JVM 教程,都是乾貨。

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

你可以使用選項 -Xms和 -Xmx 指定初始堆大小和最大堆大小。這 17 個 JVM 參數,高級 Java 必須掌握,這篇推薦看下。

如果您知道應用程序需要多少堆才能正常工作,那麼可以將 -Xms 和 -Xmx 設置爲相同的值。如果您不知道,那麼 JVM 將開始使用初始堆大小,然後增加 Java 堆,直到找到堆使用量和性能之間的平衡。

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

java -XX:+PrintFlagsFinal-version | grep MaxHeapSize

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

如果在垃圾回收(GC)上花費了太多時間,並行收集器將拋出 OutOfMemoryError 錯誤。推薦閱讀:拯救 Out Of Memory,8個案例帶你飛!

如果超過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收集器不是實時收集器。它試圖在更長的時間內以高概率實現設定的暫停時間目標,但在給定的暫停時間內並不總是絕對確定。

推薦:7 種 JVM 垃圾收集器,看完我跪了

堆佈局

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的默認選項

與其它收集器的比較

這是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 對用於類元數據的本機內存量設置上限。

最近熱文:

1、Java 14 祭出神器,Lombok 被幹掉了?

2、一週面試了 30 人,面到我心態爆炸…

3、求求你們別再寫滿屏的 try catch 了!

4、阿里發佈《Java開發手冊(泰山版)》

5、推薦一款 IDEA 代碼神器,再也不加班了!

6、微信、淘寶類掃碼登錄是怎麼實現的?

7、Spring Boot 2.3 優雅關閉新姿勢,真香!

8、Redis 到底是單線程還是多線程?

9、我天!xx.equals(null) 是什麼騷操作??

10、Struts2 爲什麼被淘汰?自己作死!

掃碼關注Java技術棧公衆號閱讀更多幹貨。

點擊「閱讀原文」帶你飛~

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