JDK 16的新增功能:ZGC

發表於2021-03-23

JDK 16已經發布,並且像往常一樣,每個新發行版都具有許多新功能,增強功能和錯誤修復。 ZGC獲得了 46個增強功能 和25個錯誤修復。在這裡,我將介紹一些更有趣的增強功能。

 

最大亞毫秒暫停

又名併發執行緒堆疊處理,當我們開始ZGC專案時,我們的目標是決不讓GC暫停花費超過10毫秒的時間。當時10毫秒似乎是一個雄心勃勃的目標。那時,HotSpot缺乏同時執行此操作所需的大量基礎結構,因此,花了幾年的時間才能達到目標。

達到最初的10ms目標後,我們重新瞄準目標,並將目標設定在更具野心的目標上。也就是說,GC暫停不得超過1ms。從JDK 16開始,我很高興地報告我們也已經實現了這一目標。

ZGC現在具有O(1)個 暫停時間。換句話說,它們以恆定的時間執行,並且不會隨堆,活動集或根集大小(或與此相關的任何其他內容)的增加而增加。

當然,我們仍然要依靠作業系統排程程式來分配GC執行緒的CPU時間。但是,只要您的系統沒有嚴重超額配置,您就可以期望看到平均GC暫停時間約為0.05毫秒(50微秒),最大暫停時間約為0.5毫秒(500微秒)。

 

如何做到?

那麼,我們是怎麼做到的?好吧,在JDK 16之前,ZGC暫停時間仍然根據根集(的子集)的大小進行縮放。更準確地說,我們仍然在“世界停止”階段掃描執行緒堆疊。這意味著,如果Java應用程式具有大量執行緒,則暫停時間將增加。如果這些執行緒具有深層呼叫堆疊,則暫停時間將增加更多。從JDK 16開始,執行緒堆疊的掃描是同時進行的,即Java應用程式繼續執行。

詳細點選標題見原文

 

就地重分配

在JDK 16中,ZGC支援就地重分配。此功能有助於在堆已填滿邊緣時GC需要收集垃圾時避免OutOfMemoryError。通常,ZGC通過將物件從稀疏填充的堆區域移動到一個或多個空堆區域中來壓縮堆(從而釋放記憶體),在這些區域中可以密集地打包這些物件。該策略簡單明瞭,非常適合並行處理。但是,它有一個缺點。它需要一定數量的可用記憶體(每種大小型別至少有一個空堆區域)才能開始重定位過程。如果堆已滿,即所有堆區域都已被使用,則我們無處可移動物件。

在JDK 16之前,ZGC通過保留堆來解決此問題。該堆保留是一組堆區域,這些堆區域已被預留,無法用於Java執行緒的常規分配。相反,在重定位物件時,僅允許GC本身使用堆保留。這確保了空的堆區域可用,即使從Java執行緒的角度來看堆已滿,也可以啟動重定位過程。堆保留通常僅佔堆的一小部分。

 

轉發表的分配和初始化

當ZGC重定位物件時,該物件的新地址記錄在轉發表中,該表是在Java堆之外分配的資料結構。每個選擇為重定位集(壓縮以釋放記憶體的堆區域的集合)的一部分的堆區域都將獲得與之關聯的轉發表。

在JDK 16之前,當重定位集非常大時,轉發表的分配和初始化可能會佔用整個GC週期時間的很大一部分。重定位集的大小與在重定位期間移動的物件數相關。例如,如果您有一個大於100GB的堆,並且工作負載導致大量碎片,並且小洞均勻分佈在整個堆中,那麼重定位集將很大,並且分配/初始化它可能需要一段時間。當然,這項工作總是在併發階段完成的,因此它從未影響過GC的暫停時間。儘管如此,這裡仍有改進的空間。

在JDK 16中,ZGC現在批量分配轉發表。現在,我們不做任何呼叫(可能成千上萬個)為malloc/new為每個表分配記憶體的方式,而是通過一次呼叫來一次性分配所有表所需的所有記憶體。這有助於避免通常的分配開銷和潛在的鎖爭用,並顯著減少分配這些表所需的時間。

 

概括

  • 通過併發執行緒堆疊掃描,ZGC現在在微秒範圍內具有暫停時間,平均暫停時間為〜50µs,最大暫停時間為〜500µs。暫停時間不受堆,活動集和根集大小的影響。
  • 堆儲備現在已經不復存在了,ZGC在需要時就地重新分配。這樣可以節省記憶體,但也可以確保在所有情況下都可以成功壓縮堆。
  • 現在可以更有效地分配和初始化轉發表,這縮短了完成GC週期所需的時間,尤其是在收集稀疏填充的大堆時。

有關ZGC的更多資訊,請參見OpenJDK WikiInside Java的GC部分 或此部落格

 

相關文章