# 背景
正如我们所知道的在JDK 11中即将迎来ZGC(The Z Garbage Collector),这是一个处于实验阶段的,可扩展的低延迟垃圾回收器。本文整合了外网几篇介绍ZGC的文章和代码。

# 目标
* 每次GC STW的时间不超过10ms
* 能够处理从几百M到几T的JAVA堆
* 与G1相比,吞吐量下降不超过15%
* 为未来的GC功能和优化利用有色对象指针(colored oops)和加载屏障(load barriers)奠定基础
* 初始支持Linux/x64

# 描述
ZGC的特点:
* 并发
* 基于Region的
* 标记整理
* NUMA感知
* 使用colored oops
* 使用load barrier
仅root扫描时STW,因此GC暂停时间不会随堆的大小而增加。

ZGC的核心原则是将load barrier与colored oops结合使用。这使得ZGC能够在Java应用程序线程运行时执行并发操作,例如对象迁移时。
从Java线程的角度来看,在Java对象中加载引用字段的行为受到load barrier的影响。除了对象地址之外,colored oops还包含load barrier使用的信息,以确定在允许Java线程使用指针之前是否需要采取某些操作。
例如,对象可能已迁移,在这种情况下,load barrier将检测情况并采取适当的操作。

与其他替代技术相比,colored oops提供了如下非常有吸引力的特性:
* 它允许ZGC在对象迁移和整理阶段回收和重用内存。这有助于降低一般堆开销。这也意味着不需要为Full GC实现一个单独的标记整理算法。
* 目前在colored oops中仅存储标记和对象迁移相关信息。然而,这种方案的通用性使我们能够存储任何类型的信息(只要我们可以将它放入指针中)并让load barrier根据该信息采取它想要的任何动作。比如,在异构内存环境中,这可以用于跟踪堆访问模式,以指导GC对象迁移策略,将很少使用的对象移动到冷存储。

ZGC可以并发执行下面的任务:
* 标记
* 引用处置
* relocation集选择
* 迁移和整理

# 性能
以下是基于同一基准的GC暂停时间。请注意,确切的数字取决于所使用的确切机器和设置。

ZGC
avg: 1.091ms (+/-0.215ms)
95th percentile: 1.380ms
99th percentile: 1.512ms
99.9th percentile: 1.663ms
99.99th percentile: 1.681ms
max: 1.681ms

G1
avg: 156.806ms (+/-71.126ms)
95th percentile: 316.672ms
99th percentile: 428.095ms
99.9th percentile: 543.846ms
99.99th percentile: 543.846ms
max: 543.846ms

# 限制
* 当前版本不支持类卸载
* 当前版本不支持JVMCI
JVMCI是JDK 9 引入的JVM编译器接口。这个接口允许用Java编写的编译器被JVM用作动态编译器。JVMCI的API提供了访问VM结构、安装编译代码和插入JVM编译系统的机制。现有支持Java编译器的项目主要是 Graal 和 Metropolis 。

# 如何工作的
## 指针标记
在x64系统上,引用是64位的, ZGC重新定义了引用结构

```
//  +-------------------+-+----+-----------------------------------------------+
//  |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      (Address view 4-8TB)
//  |                   |                                 0010 = Marked1      (Address view 8-12TB)
//  |                   |                                 0100 = Remapped     (Address view 16-20TB)
//  |                   |                                 1000 = Finalizable  (Address view N/A)
//  |                   |
//  |                   * 46-46 Unused (1-bit, always zero)
//  |
//  * 63-47 Fixed (17-bits, always zero)
```
如上表所示, ZGC使用41-0存储对象实际地址的前42位, 42位地址为应用程序提供了理论4TB的堆空间; 45-42位为metadata比特位, 对应于如下状态: finalizable,remapped,marked1和marked0; 46位为保留位,固定为0; 63-47位固定为0.

在引用中添加元数据, 使得解除引用的代价更加高昂, 因为需要操作掩码以获取真实的地址, ZGC采用了一种有意思的技巧, 读操作时是精确知道metadata值的, 而分配空间时, ZGC映射同一页到3个不同的地址,而在任一时间点,这3个地址中只有一个正在使用中。
```
for marked0: (0b0001 << 42) | x
for marked1: (0b0010 << 42) | x
for remapped: (0b0100 << 42) | x
```

实现代码如下:
```
void ZPhysicalMemoryBacking::map(ZPhysicalMemory pmem, uintptr_t offset) const {
  if (ZUnmapBadViews) {
    // Only map the good view, for debugging only
    map_view(pmem, ZAddress::good(offset), AlwaysPreTouch);
  } else {
    // Map all views
    map_view(pmem, ZAddress::marked0(offset), AlwaysPreTouch);
    map_view(pmem, ZAddress::marked1(offset), AlwaysPreTouch);
    map_view(pmem, ZAddress::remapped(offset), AlwaysPreTouch);
  }
}

void ZPhysicalMemoryBacking::unmap(ZPhysicalMemory pmem, uintptr_t offset) const {
  if (ZUnmapBadViews) {
    // Only map the good view, for debugging only
    unmap_view(pmem, ZAddress::good(offset));
  } else {
    // Unmap all views
    unmap_view(pmem, ZAddress::marked0(offset));
    unmap_view(pmem, ZAddress::marked1(offset));
    unmap_view(pmem, ZAddress::remapped(offset));
  }
}
```

采用此方法后, ZGC堆空间结构如下:
```
// Address Space & Pointer Layout
// ------------------------------
//
//  +--------------------------------+ 0x00007FFFFFFFFFFF (127TB)
//  .                                .
//  .                                .
//  .                                .
//  +--------------------------------+ 0x0000140000000000 (20TB)
//  |         Remapped View          |
//  +--------------------------------+ 0x0000100000000000 (16TB)
//  |     (Reserved, but unused)     |
//  +--------------------------------+ 0x00000c0000000000 (12TB)
//  |         Marked1 View           |
//  +--------------------------------+ 0x0000080000000000 (8TB)
//  |         Marked0 View           |
//  +--------------------------------+ 0x0000040000000000 (4TB)
//  .                                .
//  +--------------------------------+ 0x0000000000000000
```

如此带来一个副作用, ZGC无法兼容指针压缩.

## 分页
在G1中,堆内存通常被分为几千个大小相同region。同样的,在ZGC中堆内存也被分成大量的区域,它们被称为page,不同的是,ZGC中page的大小是不同的。
ZGC有3种不同的页面类型:小型(2MB大小),中型(32MB大小)和大型(2MB的倍数)。
在小页面中分配小对象(最大256KB大小),在中间页面中分配中型对象(最多4MB)。大页面中分配大于4MB的对象。大页面只能存储一个对象,与小页面或中间页面相对应。
有些令人困惑的大页面实际上可能小于中等页面(例如,对于大小为6MB的大对象)。

## 标记整理
```
void ZDriver::run_gc_cycle(GCCause::Cause cause) {
  ZDriverCycleScope scope(cause);

// Phase 1: Pause Mark Start
  {
    ZMarkStartClosure cl;
    vm_operation(&cl);
  }

// Phase 2: Concurrent Mark
  {
    ZStatTimer timer(ZPhaseConcurrentMark);
    ZHeap::heap()->mark();
  }

// Phase 3: Pause Mark End
  {
    ZMarkEndClosure cl;
    while (!vm_operation(&cl)) {
      // Phase 3.5: Concurrent Mark Continue
      ZStatTimer timer(ZPhaseConcurrentMarkContinue);
      ZHeap::heap()->mark();
    }
  }

// Phase 4: Concurrent Reference Processing
  {
    ZStatTimer timer(ZPhaseConcurrentReferencesProcessing);
    ZHeap::heap()->process_and_enqueue_references();
  }

// Phase 5: Concurrent Reset Relocation Set
  {
    ZStatTimer timer(ZPhaseConcurrentResetRelocationSet);
    ZHeap::heap()->reset_relocation_set();
  }

// Phase 6: Concurrent Destroy Detached Pages
  {
    ZStatTimer timer(ZPhaseConcurrentDestroyDetachedPages);
    ZHeap::heap()->destroy_detached_pages();
  }

// Phase 7: Concurrent Select Relocation Set
  {
    ZStatTimer timer(ZPhaseConcurrentSelectRelocationSet);
    ZHeap::heap()->select_relocation_set();
  }

// Phase 8: Prepare Relocation Set
  {
    ZStatTimer timer(ZPhaseConcurrentPrepareRelocationSet);
    ZHeap::heap()->prepare_relocation_set();
  }

// Phase 9: Pause Relocate Start
  {
    ZRelocateStartClosure cl;
    vm_operation(&cl);
  }

// Phase 10: Concurrent Relocate
  {
    ZStatTimer timer(ZPhaseConcurrentRelocated);
    ZHeap::heap()->relocate();
  }
}
```
ZGC包含10个阶段,但是主要是两个阶段标记和relocating。
GC循环从标记阶段开始,递归标记所有可达对象,标记阶段结束时,ZGC可以知道哪些对象仍然存在哪些是垃圾。ZGC将结果存储在每一页的位图(称为live map)中。

在标记阶段,应用线程中的load barrier将未标记的引用压入线程本地的标记缓冲区。一旦缓冲区满,GC线程会拿到缓冲区的所有权,并且递归遍历此缓冲区所有可达对象。注意:应用线程负责压入缓冲区,GC线程负责递归遍历。

标记阶段后,ZGC需要迁移relocate集中的所有对象。relocate集是一组页面集合,包含了根据某些标准(例如那些包含最多垃圾对象的页面)确定的需要迁移的页面。对象由GC线程或者应用线程迁移(通过load barrier)。ZGC为每个relocate集中的页面分配了转发表。转发表是一个哈希映射,它存储一个对象已被迁移到的地址(如果该对象已经被迁移)。

GC线程遍历relocate集的活动对象,并迁移尚未迁移的所有对象。有时候会发生应用线程和GC线程同时试图迁移同一个对象,在这种情况下,ZGC使用CAS操作来确定胜利者。

一旦GC线程完成了relocate集的处理,迁移阶段就完成了。虽然这时所有对象都已迁移,但是旧地引用址仍然有可能被使用,仍然需要通过转发表重新映射(remapping)。然后通过load barrier或者等到下一个标记循环修复这些引用。

这也解释了为什么对象引用中有两个标记位(marked0和marked1)。标记阶段交替使用在marked0和marked1位。

## load barrier
它的比较容易和CPU的内存屏障(memory barrier)弄混淆,但是它们是完全不同的东西。

从堆中读取引用时,ZGC需要一个所谓的load barrier(也称为read-barrier)。每次Java程序访问对象字段时,ZGC都会执行load barrier的代码逻辑,例如obj.field。访问原始类型的字段不需要屏障,例如obj.anInt或obj.anDouble。ZGC不使用存储/写入障碍obj.field = someValue。

如标记整理章节所说,根据GC当前所处的阶段,如果尚未标记或迁移引用,则屏障会标记对象或迁移它。

# 思考
## STW为什么这么短
仅root扫描时STW,其他标记、清理、迁移阶段,均通过colored oops和load-barrier配合使用,并发执行。

# 参考资料
JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)
http://openjdk.java.net/jeps/333
http://hg.openjdk.java.net/jdk/jdk/rev/767cdb97f103
http://hg.openjdk.java.net/zgc/zgc/file/59c07aef65ac/src/hotspot/os_cpu/linux_x86/zGlobals_linux_x86.hpp#l59
http://hg.openjdk.java.net/zgc/zgc/file/59c07aef65ac/src/hotspot/share/gc/z/zPage.hpp#l34

注:只为参考学习

JDK 11中的ZGC-一种可扩展的低延迟垃圾收集器的更多相关文章

  1. JVM 低延迟垃圾收集器 Shenandoah 和 ZGC

    本文部分摘自<深入理解 Java 虚拟机第三版> 概述 衡量垃圾收集器的三项指标分别是:内存占用.吞吐量和延迟.这三者共同构成一个"不可能三角",即一款优秀的收集器最多 ...

  2. 论C++11 中vector的N种遍历方法

    随着C++11标准的出现,C++标准添加了许多有用的特性,C++代码的写法也有比较多的变化. vector是经常要使用到的std组件,对于vector的遍历,本文罗列了若干种写法. (注:本文中代码为 ...

  3. C++11中vector的几种遍历方法

    假设有这样的一个vector: vector<int> line={1,2,3,4,5,6,7,8,9}; 需要输出vector里的每个元素,主函数如下: void showvec(con ...

  4. jdk 11特性

    JDK 11 总共包含 17 个新的 JEP ,分别为: 181: Nest-Based Access Control(基于嵌套的访问控制) 309: Dynamic Class-File Const ...

  5. Java 9 揭秘(20. JDK 9中API层次的改变)

    Tips 做一个终身学习的人. 在最后一章内容中,主要介绍以下内容: 下划线作为新关键字 改进使用try-with-resources块的语法 如何在匿名类中使用<>操作符 如何在接口中使 ...

  6. 号称能将STW干掉1ms的Java垃圾收集器ZGC到底是个什么东西?

    ZGC介绍 ZGC(The Z Garbage Collector)是JDK 11中推出的一款追求极致低延迟的实验性质的垃圾收集器,它曾经设计目标包括: 停顿时间不超过10ms: 停顿时间不会随着堆的 ...

  7. 在Chrome、Firefox等高版本浏览器中实现低延迟播放海康、大华RTSP

    一.背景 现在到处是摄像头的时代,随着带宽的不断提速和智能手机的普及催生出火热的网络直播行业,新冠病毒的大流行又使网络视频会议系统成为商务会议的必然选择,因此RTSP实时视频流播放及处理不再局限于安防 ...

  8. [转帖] Oracle JDK 11 正式发布.. 版本号真快

    Java 11 / JDK 11 正式发布! oschina 发布于 2018年09月26日 收藏 19 评论 38   在您的既有IT基础设施上按需构建人工智能更高效>>>   美 ...

  9. 【原】实时渲染中常用的几种Rendering Path

    [原]实时渲染中常用的几种Rendering Path 本文转载请注明出处 —— polobymulberry-博客园 本文为我的图形学大作业的论文部分,介绍了一些Rendering Path,比较简 ...

随机推荐

  1. css 通配符选择器

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  2. USB安装centos6系统(centos7需要换软件)

    一.下载系统镜像 二.下载安装软碟通软件UltraISO 三.插入U盘制作启动盘 1.用软碟通打开镜像文件:文件-->打开 2.写入映像:启动-->写入硬盘映像 3.等待写入完成 四.系统 ...

  3. python 元祖字典集合

    一.元祖 1.用途:记录多个值,当多个值没有改变的需求,元祖不能修改,但元祖里的数据的数据可以修改. 2.定义方式:在()内用逗号分隔开多个任意值. 思考:如果定义一个只有一个一个值的元祖. # t ...

  4. Platform.Uno介绍

    编者语:Xamarin国内很多人说缺乏可用的实例,我在写书过程中在完善一些常用场景的例子,希望帮到大家.Build 2018结束一周了,善友问我要不要谈谈Xamarin的一些变化,但碍于时间有限一直没 ...

  5. jQuery之标签操作和返回顶部、登录验证、全选反选、克隆示例

    一.样式操作 1.JQ中的样式类 somenode.addClass();// 添加指定的CSS类名. somenode.removeClass();// 移除指定的CSS类名. somenode.h ...

  6. #!/usr/bin/python3的作用 解决vscode ImportError: No module named xxxx

    在 Python 脚本的第一行经常见到这样的注释: #!/usr/bin/env python3 或者 #!/usr/bin/python3 含义 在脚本中, 第一行以 #! 开头的代码, 在计算机行 ...

  7. Python【第三篇】文件操作、字符编码

    一.文件操作 文件操作分为三个步骤:文件打开.操作文件.关闭文件,但是,我们可以用with来管理文件操作,这样就不需要手动来关闭文件. 实现原理: import contextlib @context ...

  8. luogu3346 诸神眷顾的幻想乡 (广义SAM)

    首先,让每一个叶节点做一次树根的话,每个路径一定至少有一次会变成直上直下的 于是对于每个叶节点作为根产生的20个trie树,把它们建到同一个广义SAM里 建法是对每个trie dfs去建,last就是 ...

  9. 洛谷P3205 [HNOI2011]合唱队 DP

    原题链接点这里 今天在课上听到了这个题,听完后觉得对于一道\(DP\)题目来说,好的状态定义就意味着一切啊! 来看题: 题目描述 为了在即将到来的晚会上有更好的演出效果,作为AAA合唱队负责人的小A需 ...

  10. SNMP源码分析之(一)配置文件部分

    snmpd.conf想必不陌生.在进程启动过程中会去读取配置文件中各个配置.其中几个参数需要先知道是干什么的: token:配置文件的每行的开头,例如 group MyROGroup v1 readS ...