JDK 11 ZGC簡介

来源:https://www.cnblogs.com/ctgulong/archive/2018/10/04/9742434.html
-Advertisement-
Play Games

JDK 11 ZGC簡介 註1:本文翻譯自這篇 "文章" 註2: 我有了新的獨立博客 "地址" ,歡迎訪問 前言 ZGC是最近由Oracle為OpenJDK開源的新垃圾收集器。它主要由Per Liden編寫。ZGC類似於 "Shenandoah" 或Azul的C4,專註於減少暫停時間的同時仍然 "壓 ...


JDK 11 ZGC簡介

註1:本文翻譯自這篇文章
註2: 我有了新的獨立博客地址,歡迎訪問

前言

ZGC是最近由Oracle為OpenJDK開源的新垃圾收集器。它主要由Per Liden編寫。ZGC類似於Shenandoah或Azul的C4,專註於減少暫停時間的同時仍然壓縮堆

雖然我不會在這裡給出完整的介紹,但“壓縮堆”只是意味著將仍然存活的對象移動到堆的其他區域.這樣做有助於減少碎片,但通常這也意味著整個應用程式(包括其所有線程)需要暫停,這通常被稱為Stop the world 。只有GC完成後,才能恢復應用程式。

在GC相關的文獻中,應用程式通常稱為mutator ,因為從GC的角度來看,應用程式會改變堆(mutates the heap)。根據堆的大小,這樣的暫停可能需要幾秒鐘,這對於互動式應用程式來說可能是難以接受的。

有幾種方法可以減少暫停時間:

  • GC可以在壓縮時使用多個線程(並行壓縮 parallel compaction)
  • 壓縮工作也可以分為多個暫停(增量壓縮 incremental compaction)
  • 壓縮堆的同時不暫停應用程式,或者只是很短時間暫停(併發壓縮 concurrent compaction)
  • Go的GC就是完全不壓縮堆

如前所述,ZGC會進行併發壓縮,這當然不是一個簡單的實現功能,因此我想描述一下這是如何工作的。為什麼這很複雜?

你需要將對象複製到另一個記憶體地址,同時另一個線程仍然可以讀寫舊對象。

如果對象已經複製成功,那麼堆中仍有許多指向舊地址的引用需要更新到新地址。

雖然併發壓縮(concurrent compaction)似乎是上述方案中降低暫停時間的最佳解決方案,但肯定會涉及一些權衡。因此,如果您不關心暫停時間,那麼最好使用專註於吞吐量的GC。

GC屏障 (GC Barriers)

理解ZGC如何進行併發壓縮的關鍵是Load barrier (通常在GC文獻中稱為Read barrier).這裡簡單介紹一下,詳細的描述請看下麵的Load Barrier一節。

如果GC有讀取屏障(Load barrier),則在從堆讀取引用時,GC需要執行一些額外操作。在Java中,也就是像執行這樣的代碼Object xxx=obj.field時需要額外操作。

對於像obj.field = value這樣的操作,GC也可能需要寫入屏障(叫做Write Barrier或者Store Barrier)[譯註:在分代GC還有引用計數中會用到寫入屏障].

這兩個操作都比較特殊因為它們在每次讀取或寫入堆時發生的。Load Barrier和Store Barrier的名稱有點令人困惑,但註意這個屏障與CPU的記憶體障礙是完全不同的兩個概念

堆中的讀取和寫入都非常常見,因此兩種GC屏障都需要非常高效,在常見情況下就是一些彙編代碼。Read barrier通常比Write Barrier大一個數量級(可能會因應用程式而異),因此Read Barrier對性能要求更高。

例如,分代GC通常只需要一個寫屏障,不需要讀屏障。ZGC則需要一個讀屏障但沒有寫屏障。對於併發壓縮,我沒有看到沒有讀取障礙的解決方案。

這裡需要註意:即使GC需要某種類型的屏障,只有在讀取或寫入堆中的引用時需要它們。讀取或寫入像int或double這樣的基本類型是不需要屏障的.

指針標記(Pointer tagging Or Colored Pointers )

ZGC在堆引用中存儲額外的元數據 ,在x64上是64 bit(ZGC目前不支持compressed oops和 class pointers)。64位中的48位用做x64上的虛擬記憶體地址 。雖然確切地說只有47位,因為第47位確定了位48-63的值(目前這些位都是0)。ZGC保留對象實際地址的前42位(在源代碼中稱為偏移量 )。42位地址理論上就會有4TB的堆大小限制。其餘的位用於這些標誌: finalizable , remapped , marked1和marked0 (保留一位用於將來使用)。如下圖所示:

 6                 4 4 4  4 4                                             0
 3                 7 6 5  2 1                                             0
+-------------------+-+----+-----------------------------------------------+
|00000000 00000000 0|0|1111|11 11111111 11111111 11111111 11111111 11111111|
+-------------------+-+----+-----------------------------------------------+
|                   | |    |
|                   | |    * 41-0 Object Offset (42-bits, 4TB address space)
|                   | |
|                   | * 45-42 Metadata Bits (4-bits)  0001 = Marked0
|                   |                                 0010 = Marked1
|                   |                                 0100 = Remapped
|                   |                                 1000 = Finalizable
|                   |
|                   * 46-46 Unused (1-bit, always zero)
|
* 63-47 Fixed (17-bits, always zero)

在堆引用中具有元數據信息使得解引用更加昂貴,因為需要mask地址以獲得沒有元信息的真實地址。ZGC採用了一個很好的技巧來避免這種情況:

當從記憶體中讀取時,會設置marked0 , marked1或remapped中的一個。

在偏移x處分配頁面(allocating a page)時,ZGC將同一頁面映射到3個不同的地址 :

  • for marked0 :(0b0001 << 42) | x
  • for marked1 : (0b0010 << 42) | x
  • for remapped : (0b0100 << 42) | x

因此,ZGC從地址4TB開始保留16TB的地址空間(但實際上並未使用所有這些記憶體)。如下圖

  +--------------------------------+ 0x0000140000000000 (20TB)
  |         Remapped View          |
  +--------------------------------+ 0x0000100000000000 (16TB)
  |     (Reserved, but unused)     |
  +--------------------------------+ 0x00000c0000000000 (12TB)
  |         Marked1 View           |
  +--------------------------------+ 0x0000080000000000 (8TB)
  |         Marked0 View           |
  +--------------------------------+ 0x0000040000000000 (4TB)

在任何時間點,只使用這三個視圖中的一個。調試時可以取消映射(unmapped)未使用的視圖來驗證正確性。

Pages & Physical & Virtual Memory

Shenandoah將堆分成大量同樣大小的區域 。除了不適合單個區域的大對象外,對象通常不會跨越多個區域。大對象被分配在多個連續區域中。我非常喜歡這種方法,因為它非常簡單。

在這方面,ZGC與Shenandoah非常相似。在ZGC的說法中,區域稱為頁面Pages

與Shenandoah的主要區別:ZGC中的頁面可以有不同的大小(但在x64上總是2MB的倍數)。

ZGC有3種不同的頁面類型: 小型 (2MB大小), 中型 (32MB大小)和大型 (2MB的倍數)。

在小頁面中分配小對象(最大256KB大小),在中型頁面中分配中型對象(最多4MB)。大頁面中分配大於4MB的對象。大頁面只能存儲一個對象.小頁面或中間頁面可以分配多個。

有些令人困惑的是大頁面實際上可能小於中等頁面(例如,對於大小為6MB的大對象)。

ZGC的另一個不錯的特性是,它還可以區分物理記憶體和虛擬記憶體。這背後的想法是通常有足夠的虛擬記憶體(ZGC總是4TB),而物理記憶體更稀缺。物理記憶體可以擴展到最大堆大小(使用-Xmx設置),因此這比4 TB的虛擬記憶體要小得多。在ZGC中分配特定大小的頁面意味著分配物理和虛擬記憶體。在ZGC中,物理記憶體不需要是連續的,虛擬記憶體空間是連續的。

為什麼說這是一個不錯的屬性?

分配連續範圍的虛擬記憶體是很容易的,因為我們通常有足夠的虛擬記憶體。但在物理記憶體中有3個大小為2MB的空閑頁面的情況很普通,但是對於大型對象分配我們需要6MB的連續記憶體。有足夠的空閑物理記憶體,但不幸的是這個記憶體是不連續的。ZGC能夠將這些非連續的物理頁面映射到單個連續的虛擬記憶體空間。如果無法映射,我們就會耗盡記憶體(發生OOM)

標記和重新安置對象(Marking & Relocating objects)

垃圾回收主要分為兩個階段:標記和重新安置(實際上不止這兩個階段,你可以查閱源碼)。

[譯註:重新安置(Relocating)指的是把對象從一個記憶體區域移到另外一個區域,重映射(Remapping)只的是把指向老的地址的引用更新到新的地址]

一次GC從標記階段開始,標記所有可到達的對象。在這個階段結束時,我們知道哪些對象仍然存活,哪些對象是垃圾。ZGC將此信息存儲在每個頁面的Live Map中。Live Map是一個點陣圖(bitmap) ,用於存儲給定索引處的對象是否可達和/或最終可達(對於具有finalize method的對象而言)。

在標記階段,應用程式線程中的load-barrier將未標記的引用推送到線程局部標記緩衝區。只要此緩衝區已滿,GC線程就可以獲得此緩衝區的所有權,並以遞歸方式遍歷此緩衝區中的所有可到達對象。在應用程式線程中標記只是將引用推送到緩衝區,GC線程負責遍歷對象圖並更新Live map.

標記階段結束後,ZGC要重新安置 Relocation set中的所有活動對象。

Relocation Set表示一組需要被回收的頁面(Pages),例如那些垃圾最多的頁面。存活的對象由GC線程或應用程式線程通過讀取屏障(Load Barrier)重新安置(relocated)(也就是放到新的地址去).ZGC為Relocation set中的每個頁面分配Forwarding table.

Forwarding table基本上是一個hash map,它存儲一個對象已被重新安置到的地址(如果該對象已經被重新安置)。

ZGC方法的優點是我們只需要為relocation set中的頁面分配forwarding table的空間.
相比之下,Shenandoah將轉髮指針存儲在每個對象本身,這樣就誰有一些額外的記憶體開銷。

GC線程遍歷 Relocation set中的存活對象,並重新安置(relocate)尚未重新安置的對象。這時可能發生應用程式線程和GC線程同時重新安置(relocate)同一個對象,在這種情況下,誰先relocate誰獲勝,ZGC使用原子CAS操作來確定勝者。

當不處於marking階段時,load-barrier會重新安置(relocates )/重新映射(remaps )從堆載入的所有引用。這確保了mutator看到的每個新引用都已指向對象的最新副本。重新映射(remaps)對象就是在forwarding table中查找新的對象地址。

一旦GC線程完成了relocation set的處理,重新安置階段就完成了。雖然這意味著所有對象都已重新安置,但通常仍會有引用指向relocation set,需要將其重新映射(remapped )到新地址。這些引用會被Load-Barrier自我修複。如果對於這些引用的讀取發生的不夠快,(也就是這段時間內,應用程式沒有讀到這些指向relocation set的引用),這些引用會在下一次mark階段給修複。這意味著標記階段還需要檢查 forward table以重新映射(remap) (但不重新安置 ,所有對象之前階段都保證被重新安置)對象到它們的新地址。

這也解釋了為什麼對象引用中有兩個標記位(marked0 和marked1 )。標記階段在標記的marked0和marked1位之間交替。在重新安置階段之後,仍可能存在未重定向(remapped)的引用,所以我們需要知道上一個gc周期的情況。如果新的標記階段使用相同的標記位,則Load-Barrier就知道該引用為已標記。

(譯註:這裡看起來像是GC周期remap和mark可以重疊,實際上確實是重疊的。如圖所示:
gc phase
更詳細的信息可以看這個Slide)

Load-Barrier

從堆中讀取引用時,ZGC需要一個所謂的load-barrier(也稱為read-barrier)。每次Java程式訪問對象類型的欄位時,我們都需要插入此load-barrier,例如obj.field 。訪問某些其他原始類型的欄位不需要屏障,例如obj.anInt或obj.anDouble 。ZGC不需要obj.field = someValue存儲/寫入障礙。

根據GC當前所處的階段(存儲在全局變數ZGlobalPhase中 ),如果尚未標記或重新安置對象,則屏障會標記對象或重新安置它

全局變數ZAddressGoodMaskZAddressBadMask
存儲對應的掩碼,該掩碼確定引用是否已被認為是好的(這意味著已經標記或重新映射/重新安置remapped/relocated)或者是否仍然需要一些操作。這些變數僅在標記開始階段和重新安置階段同時改變.ZGC源代碼中的這個表格可以很好地概述這些掩碼的狀態:

               GoodMask         BadMask          WeakGoodMask     WeakBadMask
               --------------------------------------------------------------
Marked0        001              110              101              010
Marked1        010              101              110              001
Remapped       100              011              100              011

屏障的彙編代碼可以在MacroAssembler for x64中看到,我只會為這個屏障顯示一些偽彙編代碼:

mov rax, [r10 + some_field_offset]
test rax, [address of ZAddressBadMask]
jnz load_barrier_mark_or_relocate

# otherwise reference in rax is considered good

第一個彙編指令從堆讀取引用: r10存儲對象引用, some_field_offset是一些欄位偏移常量。載入的引用存儲在rax寄存器中。

然後針對當前的壞掩碼測試該引用(這隻是一個位與)。此處不需要同步,因為ZAddressBadMask僅在STW時才更新。如果結果不為零,我們需要執行屏障。

屏障需要根據我們當前所處的GC階段標記或重新安置對象。在此操作之後, 他需要更新存儲在r10 + some_field_offset中的引用來指向新引用。這步操作是必要的,以便來該欄位的後續載入返回正確的引用。

由於我們可能需要更新引用地址,因此我們需要使用兩個寄存器r10和rax作為載入的引用和對象地址。正確的引用也需要存儲到寄存器rax中 ,這樣在後面的執行過程中我們就已經載入了正確的引用。

由於每個引用都需要標記或重新安置,因此在開始標記或重新安置階段後,吞吐量可能會立即降低。當大多數引用被修複時,這應該會變得更快。

Stop-the-World 停頓

ZGC並沒有徹底擺脫STW。收集器在開始標記,結束標記開始重新安置時需要暫停。但這種暫停通常很短,只有幾毫秒。

當開始標記時,ZGC遍歷所有線程堆棧以標記root set。root set是遍歷對象圖的開始的地方。root set通常由本地和全局變數組成,但也包括其他內部VM結構(例如JNI句柄)。

結束標記階段時需要再次暫停。在此暫停中,GC需要清空並遍歷所有線程局部標記緩衝區。由於GC可能會發現一個未標記的大型子圖,因此可能需要更長時間。ZGC試圖通過在1毫秒後停止標記階段的結束來避免這種情況。它返回到併發標記階段,直到遍歷整個對象圖,然後可以再次開始結束標記階段

啟動重新安置階段會再次暫停應用程式。此階段與開始標記非常相似,不同之處在於此階段重新安置Root Set中的對象。

結論

我希望我能簡單介紹一下ZGC。我當然無法在一篇博客文章中描述有關此GC的所有細節。如果您需要更多信息,ZGC是開源的因此可以研究整個實現。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JPA概述 JPA(Java Persistence API)的簡稱,用於持久化的API。 JAVAEE5.0平臺標準的ORM的規範使得應用程式以統一的方式訪問持久層。 JPA和Hibernate的關係 JPA是Hibernate的一個抽象,就像JDBC和JDBC驅動的關係一樣。 PA是規範:JPA ...
  • 1. 點擊菜單欄的File >New Project 2. 打開Terminal, 進入剛剛創建的路徑執行如下命令: python manage.py startapp app01 顯示效果如下: 3. 配置靜態文件路徑 4. 在view.py文件新增方法: 5. 在urls.py文件中進行路由匹配 ...
  • 一、JVM中的類載入器類型 從Java虛擬機的角度講,只有兩種不同的類載入器:啟動類載入器和其他類載入器。 1.啟動類載入器(Boostrap ClassLoader):這個是由c++實現的,主要負責JAVA_HOME/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar ...
  • 第一個SpringBoot程式 例子來自慕課網廖師兄的免費課程 "2小時學會SpringBoot" "Spring Boot進階之Web進階" 使用IDEA新建工程,選擇SpringBoot Initializr,勾選Web一路next就搭建了一個最簡單的SpringBoot工程。如下: @Spri ...
  • 基本概念 JMX(Java Management Extensions,即Java管理擴展)是一個為應用程式、設備、系統等植入管理功能的框架。JMX可以跨越一系列異構操作系統平臺、系統體繫結構和網路傳輸協議,靈活的開發無縫集成的系統、網路和服務管理應用。簡介看上去不是很直觀和明白,也可能我瞭解的太少 ...
  • 輸出結果:main start t1 -> main wait() -> t1 call notify() -> main continue 其實調用t1.start(),t1為就緒狀態,只是main方法中,t1被main線程鎖住了,t1.wait()的時候,讓當前線程等待,其實是讓main線程等待 ...
  • 1. 在urls.py的文件中導入操作正則表達式的方法:(新版的Django是使用path方法對URL進行路由分配) 2 . 在templates文件夾下的index.html添加如下代碼,進行路徑匹配:(在需要超鏈接的連接進行路由匹配) 3. 點擊超鏈接顯示的URL如下: http://127.0 ...
  • #1831 : 80 Days #1831 : 80 Days 時間限制:1000ms 單點時限:1000ms 記憶體限制:256MB 描述 80 Days is an interesting game based on Jules Verne's science fiction "Around th ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...