本文主要包括以下內容:
- 優化目標與策略(Ergonomics)
- 垃圾收集器實作(Garbage Collector Implementation)
- 影響垃圾收集性能的因素
- 總堆(Total Heap)
- 年輕代
- 可用的收集器(Available Collectors)
- 串行收集器(Serial Collector)
- 并行收集器(Parallel Collector)
- G1收集器(Garbage-First Garbage Collector)
- Z收集器(The Z Garbage Collector)
- 選擇收集器選擇收集器
- 并行收集器
- G1垃圾收集器
- 啟用G1
- 基本概念
- G1內部細節
- G1 GC的默認選項
- 與其它收集器的比較
- Z垃圾收集器
- 其它考慮因素
- 顯式垃圾回收
- 類元資料(Class Metadata)
優化目標與策略(Ergonomics)
垃圾收集器、堆和運行時編譯器默認選擇
- G1(Garbage First)收集器
- GC執行緒的最大值受限于堆大小和可用的CPU資源
- 初始堆空間為物理記憶體的1/64
- 最大堆空間為物理記憶體的1/4
- 分層編譯器,同時使用C1和C2
可以將 Java HotSpot VM 垃圾收集器配置為優先滿足兩個目標之一:最大暫停時間和應用吞吐量, 如果首選目標得到滿足,收集器將嘗試最大化其他目標,
最大暫停時間目標(Maximum Pause-Time Goal)
暫停時間是垃圾收集器停止應用程式并恢復不再使用的空間的持續時間, 最大暫停時間目標的意圖是限制這些暫停的最長時間,
使用命令列選項 -XX:MaxGCPauseMillis=<nnn> 指定最大暫停時間目標,這被解釋為向垃圾回收器提示,需要的暫停時間為 nnn 毫秒或更短, 垃圾收集器調整 Java 堆大小和其他與垃圾收集相關的引數,以使垃圾收集暫停時間小于 nnn 毫秒, 最大暫停時間目標的預設值隨收集器的不同而變化, 這些調整可能會導致垃圾收集更頻繁地發生,從而降低應用程式的總吞吐量, 但是,在某些情況下,暫停時間的預期目標無法實作,
吞吐量目標(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 可以重用它的記憶體,
理論上,最簡單的垃圾收集演算法在每次運行時遍歷每個可達物件,任何剩下的東西都被認為是垃圾,這種方法花費的時間與活躍物件的數量成正比,這對于維護大量活躍資料的大型應用程式來說是禁止的,
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=<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垃圾收集周期的各個階段,它們之間的停頓和過渡:
-
- 純年輕(Young-only)階段
- 這個階段從幾個普通(Normal)的年輕代回收開始,將物件升級到老年代, 當老年代占有率達到一定閾值時,即初始堆占有率閾值,純年輕(young-only)階段和空間回收(space-reclamation)階段開始轉換,此時,G1計劃一個并發啟動(Concurrent Start)年輕代回收,而不是普通(Normal)的年輕代回收,
- 并發啟動(Concurrent Start):這種型別的回收除了執行普通年輕代回收之外,還啟動標記(marking)程序,
- 重標記(Remark):此暫停將自行確定標記,執行全域參考處理和類卸載,回收完全空的區域并清理內部資料結構,
- 清理(Cleanup):這個暫停決定了是否會真正進入空間回收階段,
-
空間回收(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 對用于類元資料的本機記憶體量設定上限,
來源:http://dwz.date/dMSu
歡迎關注公眾號 【碼農開花】一起學習成長
我會一直分享Java干貨,也會分享免費的學習資料課程和面試寶典
回復:【計算機】【設計模式】【面試】有驚喜哦
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/244568.html
標籤:其他
下一篇:內部類
