JDK 11中的ZGC-一种可扩展的低延迟垃圾收集器
# 背景
正如我们所知道的在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-一种可扩展的低延迟垃圾收集器的更多相关文章
- JVM 低延迟垃圾收集器 Shenandoah 和 ZGC
本文部分摘自<深入理解 Java 虚拟机第三版> 概述 衡量垃圾收集器的三项指标分别是:内存占用.吞吐量和延迟.这三者共同构成一个"不可能三角",即一款优秀的收集器最多 ...
- 论C++11 中vector的N种遍历方法
随着C++11标准的出现,C++标准添加了许多有用的特性,C++代码的写法也有比较多的变化. vector是经常要使用到的std组件,对于vector的遍历,本文罗列了若干种写法. (注:本文中代码为 ...
- C++11中vector的几种遍历方法
假设有这样的一个vector: vector<int> line={1,2,3,4,5,6,7,8,9}; 需要输出vector里的每个元素,主函数如下: void showvec(con ...
- jdk 11特性
JDK 11 总共包含 17 个新的 JEP ,分别为: 181: Nest-Based Access Control(基于嵌套的访问控制) 309: Dynamic Class-File Const ...
- Java 9 揭秘(20. JDK 9中API层次的改变)
Tips 做一个终身学习的人. 在最后一章内容中,主要介绍以下内容: 下划线作为新关键字 改进使用try-with-resources块的语法 如何在匿名类中使用<>操作符 如何在接口中使 ...
- 号称能将STW干掉1ms的Java垃圾收集器ZGC到底是个什么东西?
ZGC介绍 ZGC(The Z Garbage Collector)是JDK 11中推出的一款追求极致低延迟的实验性质的垃圾收集器,它曾经设计目标包括: 停顿时间不超过10ms: 停顿时间不会随着堆的 ...
- 在Chrome、Firefox等高版本浏览器中实现低延迟播放海康、大华RTSP
一.背景 现在到处是摄像头的时代,随着带宽的不断提速和智能手机的普及催生出火热的网络直播行业,新冠病毒的大流行又使网络视频会议系统成为商务会议的必然选择,因此RTSP实时视频流播放及处理不再局限于安防 ...
- [转帖] Oracle JDK 11 正式发布.. 版本号真快
Java 11 / JDK 11 正式发布! oschina 发布于 2018年09月26日 收藏 19 评论 38 在您的既有IT基础设施上按需构建人工智能更高效>>> 美 ...
- 【原】实时渲染中常用的几种Rendering Path
[原]实时渲染中常用的几种Rendering Path 本文转载请注明出处 —— polobymulberry-博客园 本文为我的图形学大作业的论文部分,介绍了一些Rendering Path,比较简 ...
随机推荐
- css 通配符选择器
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- USB安装centos6系统(centos7需要换软件)
一.下载系统镜像 二.下载安装软碟通软件UltraISO 三.插入U盘制作启动盘 1.用软碟通打开镜像文件:文件-->打开 2.写入映像:启动-->写入硬盘映像 3.等待写入完成 四.系统 ...
- python 元祖字典集合
一.元祖 1.用途:记录多个值,当多个值没有改变的需求,元祖不能修改,但元祖里的数据的数据可以修改. 2.定义方式:在()内用逗号分隔开多个任意值. 思考:如果定义一个只有一个一个值的元祖. # t ...
- Platform.Uno介绍
编者语:Xamarin国内很多人说缺乏可用的实例,我在写书过程中在完善一些常用场景的例子,希望帮到大家.Build 2018结束一周了,善友问我要不要谈谈Xamarin的一些变化,但碍于时间有限一直没 ...
- jQuery之标签操作和返回顶部、登录验证、全选反选、克隆示例
一.样式操作 1.JQ中的样式类 somenode.addClass();// 添加指定的CSS类名. somenode.removeClass();// 移除指定的CSS类名. somenode.h ...
- #!/usr/bin/python3的作用 解决vscode ImportError: No module named xxxx
在 Python 脚本的第一行经常见到这样的注释: #!/usr/bin/env python3 或者 #!/usr/bin/python3 含义 在脚本中, 第一行以 #! 开头的代码, 在计算机行 ...
- Python【第三篇】文件操作、字符编码
一.文件操作 文件操作分为三个步骤:文件打开.操作文件.关闭文件,但是,我们可以用with来管理文件操作,这样就不需要手动来关闭文件. 实现原理: import contextlib @context ...
- luogu3346 诸神眷顾的幻想乡 (广义SAM)
首先,让每一个叶节点做一次树根的话,每个路径一定至少有一次会变成直上直下的 于是对于每个叶节点作为根产生的20个trie树,把它们建到同一个广义SAM里 建法是对每个trie dfs去建,last就是 ...
- 洛谷P3205 [HNOI2011]合唱队 DP
原题链接点这里 今天在课上听到了这个题,听完后觉得对于一道\(DP\)题目来说,好的状态定义就意味着一切啊! 来看题: 题目描述 为了在即将到来的晚会上有更好的演出效果,作为AAA合唱队负责人的小A需 ...
- SNMP源码分析之(一)配置文件部分
snmpd.conf想必不陌生.在进程启动过程中会去读取配置文件中各个配置.其中几个参数需要先知道是干什么的: token:配置文件的每行的开头,例如 group MyROGroup v1 readS ...