偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

12 張圖帶你徹底理解 ZGC

存儲(chǔ) 存儲(chǔ)設(shè)備
ZGC(Z Garbage Collector) 是一款性能比 G1 更加優(yōu)秀的垃圾收集器。ZGC 的一大創(chuàng)舉是將 GC 信息保存在了染色指針上。染色指針是一種將少量信息直接存儲(chǔ)在指針上的技術(shù)。

大家好,我是君哥。今天來聊一聊 ZGC。

ZGC(Z Garbage Collector) 是一款性能比 G1 更加優(yōu)秀的垃圾收集器。ZGC 第一次出現(xiàn)是在 JDK 11 中以實(shí)驗(yàn)性的特性引入,這也是 JDK 11 中最大的亮點(diǎn)。在 JDK 15 中 ZGC 不再是實(shí)驗(yàn)功能,可以正式投入生產(chǎn)使用了,使用 –XX:+UseZGC 可以啟用 ZGC。

ZGC 有 3 個(gè)重要特性:

  • 暫停時(shí)間不會(huì)超過 10 ms。

JDK 16 發(fā)布后,GC 暫停時(shí)間已經(jīng)縮小到 1 ms 以內(nèi),并且時(shí)間復(fù)雜度是 o(1),這也就是說 GC 停頓時(shí)間是一個(gè)固定值了,并不會(huì)受堆內(nèi)存大小影響。

下面圖片來自:https://malloc.se/blog/zgc-jdk16

  • 最大支持 16TB 的大堆,最小支持 8MB 的小堆。
  • 跟 G1 相比,對(duì)應(yīng)用程序吞吐量的影響小于 15 %。

1.內(nèi)存多重映射

內(nèi)存多重映射,就是使用 mmap 把不同的虛擬內(nèi)存地址映射到同一個(gè)物理內(nèi)存地址上。如下圖:

ZGC 為了更靈活高效地管理內(nèi)存,使用了內(nèi)存多重映射,把同一塊兒物理內(nèi)存映射為 Marked0、Marked1 和 Remapped 三個(gè)虛擬內(nèi)存。

當(dāng)應(yīng)用程序創(chuàng)建對(duì)象時(shí),會(huì)在堆上申請(qǐng)一個(gè)虛擬地址,這時(shí) ZGC 會(huì)為這個(gè)對(duì)象在 Marked0、Marked1 和 Remapped 這三個(gè)視圖空間分別申請(qǐng)一個(gè)虛擬地址,這三個(gè)虛擬地址映射到同一個(gè)物理地址。

Marked0、Marked1 和 Remapped 這三個(gè)虛擬內(nèi)存作為 ZGC 的三個(gè)視圖空間,在同一個(gè)時(shí)間點(diǎn)內(nèi)只能有一個(gè)有效。ZGC 就是通過這三個(gè)視圖空間的切換,來完成并發(fā)的垃圾回收。

2.染色指針

2.1 三色標(biāo)記回顧

我們知道 G1 垃圾收集器使用了三色標(biāo)記,這里先做一個(gè)回顧。下面是一個(gè)三色標(biāo)記過程中的對(duì)象引用示例圖:

總共有三種顏色,說明如下:

  • 白色:本對(duì)象還沒有被標(biāo)記線程訪問過。
  • 灰色:本對(duì)象已經(jīng)被訪問過,但是本對(duì)象引用的其他對(duì)象還沒有被全部訪問。
  • 黑色:本對(duì)象已經(jīng)被訪問過,并且本對(duì)象引用的其他對(duì)象也都被訪問過了。

三色標(biāo)記的過程如下:

  • 初始階段,所有對(duì)象都是白色。
  • 將 GC Roots 直接引用的對(duì)象標(biāo)記為灰色。
  • 處理灰色對(duì)象,把當(dāng)前灰色對(duì)象引用的所有對(duì)象都變成灰色,之后將當(dāng)前灰色對(duì)象變成黑色。
  • 重復(fù)步驟 3,直到不存在灰色對(duì)象為止。

三色標(biāo)記結(jié)束后,白色對(duì)象就是沒有被引用的對(duì)象(比如上圖中的 H 和 G),可以被回收了。

2.2 染色指針

ZGC 出現(xiàn)之前, GC 信息保存在對(duì)象頭的 Mark Word 中。比如 64 位的 JVM,對(duì)象頭的 Mark Word 中保存的信息如下圖:

前 62位保存了 GC 信息,最后兩位保存了鎖標(biāo)志。

ZGC 的一大創(chuàng)舉是將 GC 信息保存在了染色指針上。染色指針是一種將少量信息直接存儲(chǔ)在指針上的技術(shù)。在 64 位 JVM 中,對(duì)象指針是 64 位,如下圖:

在這個(gè) 64 位的指針上,高 16 位都是 0,暫時(shí)不用來尋址。剩下的 48 位支持的內(nèi)存可以達(dá)到 256 TB(2 ^48),這可以滿足多數(shù)大型服務(wù)器的需要了。不過 ZGC 并沒有把 48 位都用來保存對(duì)象信息,而是用高 4 位保存了四個(gè)標(biāo)志位,這樣 ZGC 可以管理的最大內(nèi)存可以達(dá)到 16 TB(2 ^ 44)。

通過這四個(gè)標(biāo)志位,JVM 可以從指針上直接看到對(duì)象的三色標(biāo)記狀態(tài)(Marked0、Marked1)、是否進(jìn)入了重分配集(Remapped)、是否需要通過 finalize 方法來訪問到(Finalizable)。

無需進(jìn)行對(duì)象訪問就可以獲得 GC 信息,這大大提高了 GC 效率。

3.內(nèi)存布局

首先我們回顧一下 G1 垃圾收集器的內(nèi)存布局。G1把整個(gè)堆分成了大小相同的 region,每個(gè)堆大約可以有 2048 個(gè)region,每個(gè) region 大小為 1~32 MB (必須是 2 的次方)。如下圖:

  • 跟 G1 類似,ZGC 的堆內(nèi)存也是基于 Region 來分布,不過 ZGC 是不區(qū)分新生代老年代的。不同的是,ZGC 的 Region 支持動(dòng)態(tài)地創(chuàng)建和銷毀,并且 Region 的大小不是固定的,包括三種類型的 Region :
  • Small Region:2MB,主要用于放置小于 256 KB 的小對(duì)象。
  • Medium Region:32MB,主要用于放置大于等于 256 KB 小于 4 MB 的對(duì)象。
  • Large Region:N * 2MB。這個(gè)類型的 Region 是可以動(dòng)態(tài)變化的,不過必須是 2MB 的整數(shù)倍,最小支持 4 MB。每個(gè) Large Region 只放置一個(gè)大對(duì)象,并且是不會(huì)被重分配的。

4.讀屏障

讀屏障類似于 Spring AOP 的前置增強(qiáng),是 JVM 向應(yīng)用代碼中插入一小段代碼,當(dāng)應(yīng)用線程從堆中讀取對(duì)象的引用時(shí),會(huì)先執(zhí)行這段代碼。注意:只有從堆內(nèi)存中讀取對(duì)象的引用時(shí),才會(huì)執(zhí)行這個(gè)代碼。下面代碼只有第一行需要加入讀屏障。

Object o = obj.FieldA
Object p = o //不是從堆中讀取引用
o.dosomething() //不是從堆中讀取引用
int i = obj.FieldB //不是引用類型




讀屏障在解釋執(zhí)行時(shí)通過 load 相關(guān)的字節(jié)碼指令加載數(shù)據(jù)。作用是在對(duì)象標(biāo)記和轉(zhuǎn)移過程中,判斷對(duì)象的引用地址是否滿足條件,并作出相應(yīng)動(dòng)作。如下圖:

標(biāo)記、轉(zhuǎn)移和重定位這些過程請(qǐng)看下一節(jié)。

讀屏障會(huì)對(duì)應(yīng)用程序的性能有一定影響,據(jù)測(cè)試,對(duì)性能的最高影響達(dá)到 4%,但提高了 GC 并發(fā)能力,降低了 STW。

5.GC 過程

前面已經(jīng)講過,ZGC 使用內(nèi)存多重映射技術(shù),把物理內(nèi)存映射為 Marked0、Marked1 和 Remapped 三個(gè)地址視圖,利用地址視圖的切換,ZGC 實(shí)現(xiàn)了高效的并發(fā)收集。

ZGC 的垃圾收集過程包括標(biāo)記、轉(zhuǎn)移和重定位三個(gè)階段。如下圖:

ZGC 初始化后,整個(gè)內(nèi)存空間的地址視圖被設(shè)置為 Remapped。

5.1 初始標(biāo)記

從 GC Roots 出發(fā),找出 GC Roots 直接引用的對(duì)象,放入活躍對(duì)象集合,這個(gè)過程需要 STW,不過STW 的時(shí)間跟 GC Roots 數(shù)量成正比,耗時(shí)比較短。

5.2 并發(fā)標(biāo)記

并發(fā)標(biāo)記過程中,GC 線程和 Java 應(yīng)用線程會(huì)并行運(yùn)行。這個(gè)過程需要注意下面幾點(diǎn):

  • GC 標(biāo)記線程訪問對(duì)象時(shí),如果對(duì)象地址視圖是 Remapped,就把對(duì)象地址視圖切換到 Marked0,如果對(duì)象地址視圖已經(jīng)是 Marked0,說明已經(jīng)被其他標(biāo)記線程訪問過了,跳過不處理。
  • 標(biāo)記過程中Java 應(yīng)用線程新創(chuàng)建的對(duì)象會(huì)直接進(jìn)入 Marked0 視圖。
  • 標(biāo)記過程中Java 應(yīng)用線程訪問對(duì)象時(shí),如果對(duì)象的地址視圖是 Remapped,就把對(duì)象地址視圖切換到 Marked0,可以參考前面講的讀屏障。
  • 標(biāo)記結(jié)束后,如果對(duì)象地址視圖是 Marked0,那就是活躍的,如果對(duì)象地址視圖是 Remapped,那就是不活躍的。

標(biāo)記階段的活躍視圖也可能是 Marked1,為什么會(huì)采用兩個(gè)視圖呢?

這里采用兩個(gè)視圖是為了區(qū)分前一次標(biāo)記和這一次標(biāo)記。如果這次標(biāo)記的視圖是 Marked0,那下一次并發(fā)標(biāo)記就會(huì)把視圖切換到 Marked1。這樣做可以配合 ZGC 按照頁(yè)回收垃圾的做法。如下圖:

第二次標(biāo)記的時(shí)候,如果還是切換到 Marked0,那么 2 這個(gè)對(duì)象區(qū)分不出是活躍的還是上次標(biāo)記過的。如果第二次標(biāo)記切換到 Marked1,就可以區(qū)分出了。

這時(shí) Marked0 這個(gè)視圖的對(duì)象就是上次標(biāo)記過程被標(biāo)記過活躍,轉(zhuǎn)移的時(shí)候沒有被轉(zhuǎn)移,但這次標(biāo)記沒有被標(biāo)記為活躍的對(duì)象。Marked1 視圖的對(duì)象是這次標(biāo)記被標(biāo)記為活躍的對(duì)象。Remapped 視圖的對(duì)象是上次垃圾回收發(fā)生轉(zhuǎn)移或者是被 Java 應(yīng)用線程訪問過,本次垃圾回收中被標(biāo)記為不活躍的對(duì)象。

5.3 再標(biāo)記

并發(fā)標(biāo)記階段 GC 線程和 Java 應(yīng)用線程并發(fā)執(zhí)行,標(biāo)記過程中可能會(huì)有引用關(guān)系發(fā)生變化而導(dǎo)致的漏標(biāo)記問題。再標(biāo)記階段重新標(biāo)記并發(fā)標(biāo)記階段發(fā)生變化的對(duì)象,還會(huì)對(duì)非強(qiáng)引用(軟應(yīng)用,虛引用等)進(jìn)行并行標(biāo)記。

這個(gè)階段需要 STW,但是需要標(biāo)記的對(duì)象少,耗時(shí)很短。

5.4 初始轉(zhuǎn)移

轉(zhuǎn)移就是把活躍對(duì)象復(fù)制到新的內(nèi)存,之前的內(nèi)存空間可以被回收。

初始轉(zhuǎn)移需要掃描 GC Roots 直接引用的對(duì)象并進(jìn)行轉(zhuǎn)移,這個(gè)過程需要 STW,STW 時(shí)間跟 GC Roots 成正比。

5.5 并發(fā)轉(zhuǎn)移

并發(fā)轉(zhuǎn)移過程 GC 線程和 Java 線程是并發(fā)進(jìn)行的。上面已經(jīng)講過,轉(zhuǎn)移過程中對(duì)象視圖會(huì)被切回 Remapped 。轉(zhuǎn)移過程需要注意以下幾點(diǎn):

如果 GC 線程訪問對(duì)象的視圖是 Marked0,則轉(zhuǎn)移對(duì)象,并把對(duì)象視圖設(shè)置成 Remapped。

如果 GC 線程訪問對(duì)象的視圖是 Remapped,說明被其他 GC 線程處理過,跳過不再處理。

并發(fā)轉(zhuǎn)移過程中 Java 應(yīng)用線程創(chuàng)建的新對(duì)象地址視圖是 Remapped。

如果 Java 應(yīng)用線程訪問的對(duì)象被標(biāo)記為活躍并且對(duì)象視圖是 Marked0,則轉(zhuǎn)移對(duì)象,并把對(duì)象視圖設(shè)置成 Remapped。

5.6 重定位

轉(zhuǎn)移過程對(duì)象的地址發(fā)生了變化,在這個(gè)階段,把所有指向?qū)ο笈f地址的指針調(diào)整到對(duì)象的新地址上。

6.垃圾收集算法

ZGC 采用標(biāo)記 - 整理算法,算法的思想是把所有存活對(duì)象移動(dòng)到堆的一側(cè),移動(dòng)完成后回收掉邊界以外的對(duì)象。如下圖:

6.1 JDK 16 之前

在 JDK 16 之前,ZGC 會(huì)預(yù)留(Reserve)一塊兒堆內(nèi)存,這個(gè)預(yù)留內(nèi)存不能用于 Java 線程的內(nèi)存分配。即使從 Java 線程的角度看堆內(nèi)存已經(jīng)滿了也不能使用 Reserve,只有 GC 過程中搬移存活對(duì)象的時(shí)候才可以使用。如下圖:

這樣做的好處是算法簡(jiǎn)單,非常適合并行收集。但這樣做有幾個(gè)問題:

因?yàn)橛蓄A(yù)留內(nèi)存,能給 Java 線程分配的堆內(nèi)存小于 JVM 聲明的堆內(nèi)存。

Reserve 僅僅用于存放 GC 過程中搬移的對(duì)象,有點(diǎn)內(nèi)存浪費(fèi)。

因?yàn)?Reserve 不能給 GC 過程中搬移對(duì)象的 Java 線程使用,搬移線程可能會(huì)因?yàn)樯暾?qǐng)不到足夠內(nèi)存而不能完成對(duì)象搬移,這返回過來又會(huì)導(dǎo)致應(yīng)用程序的 OOM。

6.2 JDK 16 改進(jìn)

JDK 16 發(fā)布后,ZGC 支持就地搬移對(duì)象(G1 在 Full GC 的時(shí)候也是就地搬移)。這樣做的好處是不用預(yù)留空閑內(nèi)存了。如下圖:

不過就地搬移也有一定的挑戰(zhàn)。比如:必須考慮搬移對(duì)象的順序,否則可能會(huì)覆蓋尚未移動(dòng)的對(duì)象。這就需要 GC 線程之間更好的進(jìn)行協(xié)作,不利于并發(fā)收集,同時(shí)也會(huì)導(dǎo)致搬移對(duì)象的 Java 線程需要考慮什么可以做什么不可以做。

為了獲得更好的 GC 表現(xiàn),JDK 16 在支持就地搬移的同時(shí),也支持預(yù)留(Reserve)堆內(nèi)存的方式,并且 ZGC 不需要真的預(yù)留空閑的堆內(nèi)存。默認(rèn)情況下,只要有空閑的 region,ZGC 就會(huì)使用預(yù)留堆內(nèi)存的方式,如果沒有空閑的 region,否則 ZGC 就會(huì)啟用就地搬移。如果有了空閑的 region, ZGC 又會(huì)切換到預(yù)留堆內(nèi)存的搬移方式。

7.總結(jié)

內(nèi)存多重映射和染色指針的引入,使 ZGC 的并發(fā)性能大幅度提升。

ZGC 只有 3 個(gè)需要 STW 的階段,其中初始標(biāo)記和初始轉(zhuǎn)移只需要掃描所有 GC Roots,STW 時(shí)間 GC Roots 的數(shù)量成正比,不會(huì)耗費(fèi)太多時(shí)間。再標(biāo)記過程主要處理并發(fā)標(biāo)記引用地址發(fā)生變化的對(duì)象,這些對(duì)象數(shù)量比較少,耗時(shí)非常短??梢娬麄€(gè) ZGC 的 STW 時(shí)間幾乎只跟 GC Roots 數(shù)量有關(guān)系,不會(huì)隨著堆大小和對(duì)象數(shù)量的變化而變化。

ZGC 也有一個(gè)缺點(diǎn),就是浮動(dòng)垃圾。因?yàn)?ZGC 沒有分代概念,雖然 ZGC 的 STW 時(shí)間在 1ms 以內(nèi),但是 ZGC 的整個(gè)執(zhí)行過程耗時(shí)還是挺長(zhǎng)的。在這個(gè)過程中 Java 線程可能會(huì)創(chuàng)建大量的新對(duì)象,這些對(duì)象會(huì)成為浮動(dòng)垃圾,只能等下次 GC 的時(shí)候進(jìn)行回收。

責(zé)任編輯:武曉燕 來源: 君哥聊技術(shù)
相關(guān)推薦

2022-07-11 11:06:11

RocketMQ函數(shù).消費(fèi)端

2021-05-18 06:55:07

Java AQS源碼

2022-07-04 11:06:02

RocketMQ事務(wù)消息實(shí)現(xiàn)

2021-12-06 07:15:47

Pulsar地域復(fù)制

2020-10-16 06:30:45

分布式場(chǎng)景方案

2020-11-27 06:28:55

Spring循環(huán)依賴

2021-08-15 18:59:13

垃圾收集器JDK

2020-11-13 10:29:37

流程控制語(yǔ)句

2022-04-11 11:55:34

架構(gòu)技術(shù)調(diào)優(yōu)

2023-04-11 08:35:22

RocketMQ云原生

2022-06-11 18:15:26

KubernetesDockerLinux

2022-06-13 11:05:35

RocketMQ消費(fèi)者線程

2024-07-03 08:28:44

HWKafkaLEO

2021-04-25 10:45:59

Docker架構(gòu)Job

2020-11-03 10:32:48

回調(diào)函數(shù)模塊

2021-10-22 09:28:15

開發(fā)技能代碼

2022-06-27 11:04:24

RocketMQ順序消息

2019-07-24 08:49:36

Docker容器鏡像

2022-10-20 08:31:33

加鎖解鎖代碼

2020-06-28 07:39:44

Kafka分布式消息
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)