瞭解JDK的新型超快垃圾收集器:Shenandoah、ZGC和改進的G1 - oracle

banq發表於2019-12-10

ZGC,Shenandoah和對G1的改進使開發人員比以往任何時候都更接近無暫停時間。

在過去六個月中發生的一些最令人振奮的事態發展都在JDK的垃圾收集器(GC)的不斷演進中,首先,我們將介紹Shenandoah,這是一種低延遲GC,主要與應用程式同時執行;我們還將介紹作為JDK 12的一部分發布的ZGC(Java 11中引入的低延遲併發GC)的最新改進;我們將詳細解釋從Java 9開始的作為預設GC的G1的兩項改進。

Shenandoah

Shenandoah是作為JDK 12的一部分發布的新GC。實際上,Shenandoah的開發工作也向後移植了對JDK 8u和11u版本的改進。

Shenandoah團隊已針對SpecJBB基準釋出了暫停時間延遲的基準評分,該基準將延遲與JDK 9時代的G1進行了比較。結果有很大提升。

瞭解JDK的新型超快垃圾收集器:Shenandoah、ZGC和改進的G1 - oracle

Shenandoah在G1之上的主要進步是在執行應用程式執行緒的同時完成了更多的垃圾回收週期工作。G1只能在應用程式暫停時撤離其堆區域(即移動物件),而Shenandoah可以與應用程式同時重新放置物件。

為了實現併發重定位,它使用了所謂的Brooks指標。該指標是Shenandoah堆中每個物件都具有的一個附加欄位,它指向物件本身。Shenandoah之所以這樣做是因為,當它移動一個物件時,它還需要修復堆中所有引用該物件的物件。當Shenandoah將物件移動到新位置時,它將舊的Brooks指標留在原處,從而將引用轉發到該物件的新位置。當引用物件時,應用程式將轉發指標跟隨到新位置。最終,需要清除帶有轉發指標的舊物件,但是通過將清除操作與移動物件本身的步驟分離,Shenandoah可以更輕鬆地完成物件的併發重定位。

要從Java 12開始使用Shenandoah,請使用以下選項啟用它:

-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

如果您還不能升級到Java 12,但是您對試用Shenandoah感興趣,那麼可以使用向Java 8和Java 11的反向移植。值得注意的是,Oracle隨附的JDK構建中未啟用Shenandoah,但是其他OpenJDK發行商預設情況下啟用了Shenandoah。

在併發GC方面,Shenandoah不是唯一的選擇。ZGC是OpenJDK附帶的另一個GC(包括Oracle的內部版本),並且已在JDK 12中進行了改進。因此,如果您的應用程式遇到垃圾回收暫停問題,並且您正在考慮嘗試Shenandoah,則還應該注意在ZGC,我們將在下面進行介紹。

具有併發類解除安裝的ZGC

ZGC的主要目標是低延遲,可伸縮性和易用性。為此,ZGC允許Java應用程式在執行除執行緒堆疊掃描之外的所有其他垃圾回收操作時繼續執行。它可以從數百MB擴充套件到TB大小的Java堆,同時始終保持非常低的暫停時間(通常在2 ms之內)。

對於應用程式開發人員和系統架構師而言,意想不到的低暫停時間可能會產生深遠的影響。開發人員將不再需要擔心設計複雜的方法來避免垃圾回收暫停。而且,系統架構師將不需要專業的GC效能調整專業知識來實現​​可靠的低暫停時間,這對於許多用例而言都是非常重要的。這使得ZGC非常適合需要大量記憶體(例如大資料)的應用程式。但是,對於需要可預測且極短的暫停時間的較小堆,ZGC也是不錯的選擇。

ZGC已作為實驗功能新增到JDK 11中。在JDK 12中,ZGC新增了對併發類解除安裝的支援,從而允許Java應用程式在解除安裝未使用的類期間繼續執行,而不是暫停執行。

執行併發的類解除安裝非常複雜,因此,傳統上,類解除安裝是在整個JVM世界停下來的暫停中完成的。首先要確定不再使用的類集,首先需要執行引用處理。然後是終結器的處理-這就是我們指代該方法的實現的Object.finalize() 方式。作為引用處理的一部分,必須遍歷終結器可訪問的物件集,因為終結器可以通過無限制的連結鏈來傳遞類,使類保持活動狀態。不幸的是,訪問終結器可到達的所有物件可能需要很長時間。在最壞的情況下,可以通過單個終結器訪問整個Java堆。ZGC與Java應用程式同時執行引用處理(因為JDK 11中引入了ZGC)。

引用處理完成後,ZGC知道不再需要哪些類。下一步是清除由於這些類死亡而導致的包含過時和無效資料的所有資料結構。清除從活動資料結構到無效或無效資料結構的連結。為此取消連結操作需要走的資料結構包括幾個內部JVM資料結構,例如程式碼快取(包含所有JIT編譯的程式碼),類載入器資料圖,字串表,符號表,概要檔案資料等。 。取消連結失效資料結構後,將再次遍歷那些失效資料結構以將其刪除,以便最終回收記憶體。

到現在為止,所有JDK GC都通過停止操作來完成所有這些操作,從而導致Java應用程式出現延遲問題。對於低延遲GC,這是有問題的。因此,ZGC現在與Java應用程式同時執行所有這些功能,因此,不因支援類解除安裝而造成任何延遲損失。實際上,引入的用於執行併發類解除安裝的機制進一步提高了延遲。

ZGC當前可作為實驗性GC用於Linux / x86 64位平臺,從Java 13開始,可在Linux / Aarch上使用。您可以使用以下命令列選項啟用它:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

G1改進

G1也取得了一些改進。G1收集器將其垃圾收集週期分為多個不同的暫停時間。

分配物件後,最初將物件視為“年輕”一代的一部分。當它們在多個垃圾回收週期中存活時,它們最終將“保有”,然後被視為“舊”。G1中的不同區域僅包含一代人的物件,因此可以稱為年輕區域或舊區域。

為了使G1達到暫停時間目標,它需要能夠識別出可以在暫停時間目標內完成的工作,並在暫停目標期滿之前完成工作。G1具有一組複雜的啟發式方法來識別正確的工作量,這些啟發式方法擅長預測所需的工作時間,但它們並不總是準確的。G1不能僅收集年輕地區的一部分,這使情況更加複雜。它通過一次垃圾收集通行證收集了所有年輕地區。

在Java 12中,通過新增中止G1收集暫停的功能來改善了這種情況。G1跟蹤其啟發式演算法預測要收集的區域數量的準確性,並在需要時僅進行可中止的垃圾收集。通過將收集集(將在一個週期中進行垃圾收集的區域集)分成兩組進行處理:強制區域和可選區域。

強制性區域始終在GC週期內收集。在時間允許的情況下收集可選區域,如果在沒有收集可選區域的情況下超時,則收集通道將中止。強制性區域是所有較年輕的區域,可能還有一些較舊的區域。將舊區域新增到此集中以響應兩個標準。新增一些以確保可以繼續疏散物件,而新增一些以耗盡預期的暫停時間。

啟發式計算可通過將集合集合候選中的區域數除以的值來計算要新增的區域數-XX:G1MixedGCCountTarget。如果G1預測將有更多的時間來收集更多的舊區域,則它還將更多區域新增到強制區域集中,直到期望用盡可用暫停時間的80%。

這項工作的結果意味著G1可以中止或結束其混合GC迴圈。這導致較低的GC暫停等待時間,並且極有可能使G1更頻繁地達到其暫停時間目標。JEP 344中詳細介紹了這種改進。

迅速返回未使用的記憶體

對Java的最普遍的批評之一是它是一種記憶體消耗,現在不是了。

有時,通過命令列選項為JVM分配了超出其所需記憶體的記憶體。如果未提供與記憶體相關的命令列選項,則JVM可能分配了比所需更多的記憶體。分配未使用的RAM會浪費金錢,尤其是在對所有資源進行計量和適當計算的雲環境中。但是,可以採取什麼措施解決這種情況,並且可以在資源消耗方面改進Java?

常見的情況是JVM必須處理的工作負載隨時間而變化:有時需要更多的記憶體,有時需要更少的記憶體。實際上,這通常是無關緊要的,因為JVM傾向於在啟動時分配大量記憶體,即使在不需要時也會貪婪地保留它。在理想情況下,未使用的記憶體可以從JVM返回到作業系統,以便其他應用程式或容器可以使用它。

從Java 12開始,現在可以返回未使用的記憶體。

G1已經具有釋放未使用的記憶體的功能,但只有在完整的垃圾回收過程中才可以釋放它。完全垃圾收集通道通常很少發生,並且是不希望發生的,因為它們可能導致應用程式長時間停頓。在JDK 12中,G1獲得了在併發垃圾回收過程中釋放未使用記憶體的功能。此功能對於大多數為空的堆特別有用。當堆幾乎為空時,GC週期可能需要一段時間才能獲取記憶體並將其返回給作業系統。為了確保將記憶體迅速返回作業系統,從Java 12開始,如果在命令列指定的時間段內未發生垃圾回收週期,G1將嘗試觸發併發的垃圾回收週期。G1PeriodicGCInterval論點。然後,該併發的垃圾回收週期將在該週期結束時將記憶體釋放給作業系統。

為了確保這些定期的併發垃圾收集傳遞不會增加不必要的CPU開銷,它們僅在系統部分空閒時才執行。用於觸發併發週期是否執行的度量是平均一分鐘系統負載值,該值必須低於所指定的值G1PeriodicGCSystemLoadThreshold。

 

相關文章