☕【JVM技術指南】「JVM總結筆記」Java虛擬機垃圾回收認知和調優的"思南(司南)"【下部】


承接上文

(完結撒花1-52系列)☕【JVM技術指南】「JVM總結筆記」Java虛擬機垃圾回收認知和調優的"思南(司南)"【上部】

並行收集器

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

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

並行垃圾收集器線程數

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

並行收集器中分代的排列

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

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

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

指定並行收集器行為的選項
  • 最大垃圾收集暫停時間: 使用命令行選項 -XX:MaxGCPauseMillis=<N>指定最大暫停時間目標,這被解釋為需要毫秒或更少的暫停時間;默認情況下,沒有最大暫停時間目標。

  • 如果指定了暫停時間目標,則會調整堆大小和與垃圾收集有關的其他參數,以使垃圾收集暫停時間短於指定值。

  • 可能並不總是能夠達到所需的暫停時間目標

這些調整可能會導致垃圾收集器降低應用程序的總吞吐量。

吞吐量

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

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

使用選項 -Xmx 指定最大堆內存占用,此外,收集器還有一個隱式目標,即在滿足其他目標的情況下最小化堆的大小。

並行收集器目標的優先級

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

  • 首先實現最大暫停時間目標。只有在滿足了這個要求之后,吞吐量目標才能實現。 同樣,只有在前兩個目標已經實現之后,才會考慮內存大小目標。

並行收集器默認堆大小

  • 除非在命令行中指定了初始堆大小和最大堆大小,否則將根據計算機上的內存量計算它們。默認的最大堆大小是物理內存的1/4,而初始堆大小是物理內存的1/64。

  • 默認分配給年輕代的最大空間是總堆大小的1/3。

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

  • 你可以使用選項和 -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將堆分為(虛擬的)年輕代和老年代。
  • 空間回收的努力集中在年輕代身上,這樣做效率最高,偶爾的空間回收在老年代中。
G1的設計原則
  • G1的設計原則是"首先收集盡可能多的垃圾(Garbage First)"。因此,G1並不會等內存耗盡(串行、並行)或者快耗盡(CMS)的時候開始垃圾收集,而是在內部采用了啟發式算法,在老年代找出具有高收集收益的分區進行收集。

  • G1采用內存分區(Region)的思路,將內存划分為一個個相等大小的內存分區,回收時則以分區為單位進行回收,存活的對象復制到另一個空閑分區中。由於都是以相等大小的分區為單位進行操作,因此G1天然就是一種壓縮方案(局部壓縮);

同時G1可以根據用戶設置的暫停時間目標自動調整年輕代和總堆大小,暫停目標越短年輕代空間越小、總空間就越大;

  • G1雖然也是分代收集器,但整個內存分區不存在物理上的年輕代與老年代的區別,也不需要完全獨立的survivor(to space)堆做復制准備。G1只有邏輯上的分代概念,或者說每個分區都可能隨G1的運行在不同代之間前后切換;

  • G1的收集都是STW的,但年輕代和老年代的收集界限比較模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年輕代分區(年輕代收集),也可能在收集年輕代的同時,包含部分老年代分區(混合收集),這樣即使堆內存很大時,也可以限制收集范圍,從而降低停頓。

應用程序停止的其他操作會花費更多時間,比如全局標記之類的整堆操作會與應用程序並行執行。 為了使stop-the-world在空間回收方面的停頓時間縮短,G1逐步並行地進行空間回收。

G1通過跟蹤以前應用程序行為的信息和垃圾收集暫停來構建相關成本的模型,從而實現可預測性。它利用這個信息來計算停頓時所做的工作量。例如,G1首先在效率最高的區域回收空間(這些區域大部分都是垃圾,因此取名為 G1)。

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

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

堆布局

G1將堆划分為一組大小相同的堆區域Region,每個區域都有一個連續的虛擬內存范圍,Region區域是內存分配和內存回收的單位。

  • 在任何給定的時間,這些區域中的每一個都可以是空的(淺灰色) ,或者分配給特定的一代,年輕的或老年的。

  • 當內存請求進入時,內存管理器分配空閑區域。內存管理器將它們分配給一個代,然后將它們作為可用空間返回給應用程序,應用程序可以將其分配給自己。

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

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

垃圾回收周期

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

下面的列表詳細描述了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暫停期間提供更好的延遲。在這個標記期間過於保守地考慮活動對象將在下一個標記期間被回收。

  • -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虛擬機中有一個內部表示,稱為類元數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM