暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

ZGC 之执行流程分析

wangzaixiang 2018-09-08
966
JDK11呼之欲出,在这个版本中,最为吸引我的一个特性就是 ZGC 了(https://wiki.openjdk.java.net/display/zgc/Main)。ZGC 试图提供一个在TB级别的内存下,仍然可以实现10ms以下的停顿时间,同时,不会有很大的吞吐量牺牲,这对于OLTP来说,无疑是一个福音。

目前,有关ZGC的文档还不是很多,我在阅读 http://cr.openjdk.java.net/~pliden/slides/ZGC-FOSDEM-2018.pdf 等文档时,还是有很多不理解的地方,通过一些实验,以及对源代码进行分析,本文试图还原出一个完整的GC 流程,如果你对ZGC的内部实现感兴趣的话,可以结合本文,来更好的理解ZGC的完整流程中,是如何完成Color的转换的。

本文选择的一个测试程序如下:

public class Hello {

public static void main(String args[]) throws Exception {
int i = 0;
System.out.println("beginning");

while(true){
mem1();
i++;
System.out.println("loop for " + i);
}
}

static String mkString(int len) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < len; i++) sb.append('x');
return sb.toString();
}

static String[] mem1() throws Exception {
String[] arr = new String[1000];
java.util.Random r = new java.util.Random();
for(int i = 0; i < 1000; i++) {
arr[i] = mkString(r.nextInt(50) + 20);
}
Thread.sleep(10);
return arr;
}

}

编译如上程序后,使用如下命令行,既可以跟踪整个的 GC 过程:/root/jdk/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xms512m -Xmx512m -Xlog:gc*=debug::u: Hello


  1. JVM 启动时, GoodMask = Remapped

    [0.054s] Initializing The Z Garbage Collector
    [0.054s] Version: 12-internal+0-adhoc..zgc-4bbc9ad465a5 (release)
    !!! set_good_mask 100000000000 goodMask = 100000000000 bad = 2c0000000000
    [0.054s] NUMA Support: Disabled
    [0.054s] CPUs: 4 total, 4 available
    [0.055s] Memory: 3947M

  2. Warmup 过程

    zgc 有一个很慢的warm过程,具体做什么,还有待研究,在我们的这个项目中,花了接近10s,才开始完成warm,开始执行应用代码。
    [9.924s] Allocation Rate: 20.000MB/s, Avg: 6.000(+/-0.980)MB/s[9.924s] Rule: Warmup 10%, Used: 46MB, UsedThreshold: 51MBbeginning[10.025s] Allocation Rate: 0.000MB/s, Avg: 6.000(+/-0.980)MB/s[10.025s] Rule: Warmup 10%, Used: 46MB, UsedThreshold: 51MBloop for 1[10.125s] Allocation Rate: 20.000MB/s, Avg: 8.000(+/-1.200)MB/s[10.125s] Rule: Warmup 10%, Used: 48MB, UsedThreshold: 51MBloop for 2loop for 3

  3. 开始GC0,进入到 Pause Mark Start阶段。耗时 4.7ms。

    Pause Mark Start 阶段时一个 STW操作,这个阶段的主要任务:

    [10.325s] GC(0) Heap before GC invocations=0 (full 0): ZHeap           used 52M, capacity 512M, max capacity 512M
    [10.325s] GC(0)  Metaspace       used 6858K, capacity 6911K, committed 7168K, reserved 8192K
    [10.325s] GC(0) Garbage Collection (Warmup)
    [10.326s] GC(0) Pause Mark Start
    !!! set_good_mask 80000000000 goodMask = 80000000000 bad = 340000000000
    [10.327s] GC(0) Using 1 mark stripes
    [10.327s] GC(0) Mark Worker/Stripe Distribution
    [10.327s] GC(0)   Worker 0(1) -> Stripe 0(1)
    [10.327s] GC(0) Pause Roots Setup (VM Thread)
    [10.331s] GC(0) Pause Mark Start 4.742ms

    1. 暂停所有的应用线程

    2. 将 GoodMask 切换到 Marked1

    3. 从root集合开始标记所有的对象

  4. 进入到 GC0 的 Concurrent Mark 阶段,耗时 6.4ms(这个耗时根据堆的大小而变化,对较大的Heap,由于需要遍历,时间应该是比较长的。)

    Mark阶段是与应用线程并行进行的,这个从日志中可以看得出来,这个阶段的主要任务是:

    [10.331s] GC(0) Concurrent Mark
    [10.331s] GC(0) Executing Task: ZMarkTask, Active Workers: 1
    loop for 20[10.332s] GC(0) Concurrent Mark (ZWorker#1)
    [10.338s] GC(0) Concurrent Mark 6.401ms

  • 从root 结合出发,遍历heap,对每一个对象进行标记。被标记的对象是live的。这个遍历算法另行分析

  • 当前的GoodMask是Marked1,当前Heap中的引用有两个颜色:Remapped(bad)、Marked1(Good)。

  • 此时,如果应用线程中访问一个 bad colored 引用(a = obj.field1 ),将通过 Read Barrier 完成颜色重写。field1 将修改为 Marked1,对应的 a 也调整为 Marked1。

  • 此时,新分配的对象引用都具有颜色 Marked1

  • 在 Mark 阶段,是否会对所有遍历的引用,都完成颜色重写的过程呢?目前的猜测是的,这样的话,在Mark结束后,所有的活跃饮用都具有颜色 Marked1。

  • 进入到 GC0. Pause Mark End阶段。这是一个STW阶段,耗时 1.14ms。

    [10.338s] GC(0) Pause Mark End
    [10.338s] GC(0) Pause Roots Setup (VM Thread)
    ...
    [10.339s] GC(0) Pause Mark End 1.140ms

    • 这个阶段的主要任务是对Weak roots进行清理。具体待分析。

  • 进入到 GC0. Concurrent Prepare for Reloc 阶段。

    这个阶段是并行的,这个可以从日志中,看出来,有应用的日志产生了。这个阶段用时一般较短。主要任务:

    [10.339s] GC(0) Concurrent Process Non-Strong References
    ...
    [10.341s] GC(0) Concurrent Select Relocation Set
    loop for 21
    [10.346s] GC(0) Relocation Set (Medium Pages): 0->0, 0 skipped
    ...
    [10.347s] GC(0) Concurrent Prepare Relocation Set 0.609ms

    • 对 reference 进行处理

    • 清理 weak reference 

    • 选择 relocatin set,并为其准备好 forward table

  • 进入到 GC0的Pause Relocate Start阶段

    这是一个STW阶段,需要暂停的所有的应用线程,对Root集合进行 relocate处理。主要任务:

    这个阶段结束以后,会达到如下状态:

    [10.347s] GC(0) Pause Relocate Start
    !!! set_good_mask 100000000000 goodMask = 100000000000 bad = 2c0000000000
    [10.347s] GC(0) Pause Remap TLABS (VM Thread)
    ......
    [10.348s] GC(0) Pause Relocate Start 1.429ms

    • 当前Heap中存在两种颜色:Marked1(bad color)、Remapped(good color)。大部分处在 bad color。

    • root 集合的颜色全部是 good color(如果对象在relocation set中,则已完成了重定位)。

    • 切换Good Color颜色为 Remapped。

    • 遍历所有的 root 集合,将root集合的全部饮用调整为 Remapped。

    • 进行颜色切换的时候,需要检查这个指针是否在relocation set中,如果在的话,则进行重定位工作。

  • 进入到 GC0 的 Concurrent Relocate 阶段。

    承接上一阶段,本阶段的主要目的是完成对relocation set的重定位工作,即将选择进行回收的空间中的全部对象,移动到一个新的地址中,完成后,原来的这个area就可以被释放了。这个阶段一般也会比较快。由于是与应用线程并行的,并且目前存在两种颜色:

    本阶段结束后,Heap 会达到如下状态:

    [10.349s] GC(0) Concurrent Relocate
    [10.349s] GC(0) Executing Task: ZRelocateTask, Active Workers: 1
    [10.352s] GC(0) Concurrent Relocate 2.817ms

    • 大部分对象是 Marked1(bad color)

    • 部分对象是 Remapped(good color)。全部的root应用已调整为 good。

    • relcocation set 的对象已经全部移动到新的位置,但指向这些对象的引用大部分还没有被更新。

    • relocation set所占用的area 可以整块区间被释放掉。

    • 大部分对象是Marked1(bad color)

    • 部分对象是 Remapped(good color)。全部的root应用已调整为 good。

    • 在这个过程中,应用线程如果访问到 bad color 对象( a = obj.field ),将通过load barrier完成颜色的重写。重写的过程中可能会完成对象的重定位。

    • 在这个过程中,GC 线程会完成将 重定位的对象复制到新的空间的工作,并且建立 forward table。

  • 完成了 Concurrent Relocate 后,GC0 就告一段落。此时的Heap状态是:

    • 由于已经释放了足够的空间,应用线程可以高速的执行

    • 大部分对象是 Marked1(bad color)

    • 部分对象是 Remapped(good color)。全部的root应用已调整为 good。

    • 有一个当前的 forward table,记录了移动前后的对象映射关系

    • 当应用线程访问一个bad color的过程时,load barrier 会自动的将其更新为 Remapped。

  • 应用线程执行 4s 之后,再次出现GC需求,此时启动 GC1

    • 这个过程与GC0 是相似的。不同之处,GC0刚开始时,所有的引用都是 Good Color,而GC1 开始时,heap中 混杂着 Good Color + Bad Color(Marked1)。

  • GC1. Pause Mark Start。

    • Remapped. 这些引用是上一个GC阶段后正常的引用,只需要简单的切换为 Marked 0即可。

    • Marked1:这些引用是上一个GC阶段后,还没有完成 relocate 的,如果后续要切换为 Marked0,还需要进行relocate 的地址转换。

    • Marked0: Good Color:所有的root颜色已经是 Marked0

    • 切换 Good Color 为 Marked 0,此时系统中混杂着3中颜色:

  • GC1. Concurrent Mark

    • 这个阶段的目标是遍历heap,主要进行两个操作。

    • Mark 标记所有的活跃的对象。

    • Remap 将所有的引用颜色修正为 Marked0(包括从 Remapped -> Marked0, 从 Marker1 -> Marked0 )

    • Mark 和 Remap 两者可以同时进行

  • GC1. Pause Mark End

    • 自此之后的处理,与 GC0的完全一致。


    文章转载自wangzaixiang,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论