# 背景
正如我们所知道的在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. SpringCloud入门(一)

    一.微服务概述 1.什么是微服务 目前的微服务并没有一个统一的标准,一般是以业务来划分将传统的一站式应用,拆分成一个个的服务,彻底去耦合,一个微服务就是单功能业务,只做一件事. 与微服务相对的叫巨石 ...

  2. webpack2.0 css文件引入错误解决及图片输出在根目录配置问题

    webpack引入css文件,main.js内容如下 import Vue from 'vue'; import App from './App.vue'; import Mint from 'min ...

  3. python 基础篇练习题

    一.练习题 # 1.统计元组中所有数据属于字符串的个数,提示:isinstance() # 数据:t1 = (1, 2, '3', '4', 5, '6') # 结果:3 # 2.将以下数据存储为字典 ...

  4. ORM基础之ORM介绍和基础操作

    一.ORM介绍 1.ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM是通过 ...

  5. 如何破解加密了的word文档

    https://blog.csdn.net/huangbaokang/article/details/79630223 变成xml文件格式之后--查找在documentProtection前面加上un ...

  6. python学习日记(OOP——静态方法和类方法)

    classmethod 类方法在Python中使用比较少,类方法传入的第一个参数为cls,是类本身.并且,类方法可以通过类直接调用,或通过实例直接调用.但无论哪种调用方式,最左侧传入的参数一定是类本身 ...

  7. python学习日记(OOP——@property)

    在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改: s = Student() s.score = 9999 这显然不合逻辑.为了限制score的 ...

  8. MySQL的SQL_Mode修改小计

    问题复现 今天突然发现MySQL服务器升级之后sql_mode变成宽松摸索了,危害如下: 临时解决 set global sql_mode='strict_trans_tables'(阿里服务器默认是 ...

  9. MongoDB用户及数据库管理命令

    1.用户管理: 连接数据库: mongo 127.0.0.1:27017 切换到admin数据库: > use admin 创建管理员账户: db.createUser( { user: &qu ...

  10. Spring Cloud Data Flow 中的 ETL

    Spring Cloud Data Flow 中的 ETL 影宸风洛 程序猿DD 今天 来源:SpringForAll社区 1 概述 Spring Cloud Data Flow是一个用于构建实时数据 ...