Java JDK11中使人兴奋的ZGC

Java 11最近已发布,包含一些很是棒的功能。该版本包含一个全新的垃圾收集器ZGC,它由Oracle开发,承诺在数TB的堆上具备很是低的暂停时间。在本文中,咱们将介绍新GC的动机,技术概述以及ZGC开启的一些很是使人兴奋的可能性。java

那么为何须要新的GC呢?Java 10已经搭载了四艘通过多年实战测试且几乎能够无限调优。为了正确看待这一点,G1最新的Hotspot GC是在2006年推出的。当时最大的AWS实例是最初的m1.small包装1 vCPU和1.7GB内存,今天AWS很乐意租给你一个x1e.32x大型,128个vCPU,使人难以置信的3,904GB内存的服务器。ZGC的设计针对这些容量广泛存在的将来:多TB容量,暂停时间低(<10ms),对总体应用性能有影响(吞吐量 GC术语linux

为了理解ZGC在哪里适合现有的收集器,以及它如何可以作到这一点,咱们首先须要回顾一些术语。垃圾收集最基本的工做是肯定再也不使用的内存,并使其能够重用。现代收藏家是分几个阶段进行这一过程的,他们每每被描述为:服务器

  • 并行 - 在JVM运行时,有应用程序线程和垃圾收集器线程。并行阶段是由多个gc线程执行的阶段,即工做在它们之间分开。它没有说明gc线程是否可能与正在运行的应用程序线程重叠。
  • 串行 - 串行的阶段仅在单个gc线程上执行。与上面的并行同样,它没有说明工做是否与当前运行的应用程序线程重叠。
  • Stop The World - Stop The World阶段,应用程序线程被暂停,以便gc执行其工做。当您遇到GC暂停时,这是因为Stop The World阶段。
  • 并发 - 若是一个阶段是并发的,那么GC能够在应用程序线程正在进行其工做的同时执行其工做。并发阶段很复杂,由于它们须要可以处理应用程序线程,从而可能在阶段完成以前使其工做无效。
  • 增量 - 若是一个阶段是增量的,那么它能够运行一段时间并因为某些条件而提早终止,例如时间预算或须要执行的更高优先级的gc阶段,同时仍然完成了生产性工做。这与须要彻底完成的阶段造成鲜明对比。

固有的权衡取舍并发

值得指出的是,全部这些属性都须要权衡利弊。例如,并行阶段将利用多个gc线程来执行工做,但这样作会致使线程之间协调的开销。一样,并发阶段不会暂停应用程序线程,但可能涉及更多的开销和复杂性,以处理应用程序线程同时使其工做无效。ide

ZGC性能

如今咱们了解不一样gc阶段的属性,让咱们探讨ZGC的工做原理。为了实现其目标,ZGC使用了Hotspot Garbage Collectors的两种新技术:彩色指针和负载障碍。测试

指针着色优化

指针着色是一种将信息存储在指针(或Java术语,引用)自己中的技术。这是可能的,由于在64位平台上(ZGC仅为64位),指针能够处理比系统实际拥有的内存更多的内存,所以可使用其余一些位来存储状态。ZGC将本身限制在须要42位的4Tb堆中,只留下22位可能的状态,目前它使用4位:finalizable、remap、mark0和mark1。咱们稍后会解释它们的用途。spa

指针着色的一个问题是,当您须要取消引用指针时,它能够建立额外的工做,由于您须要屏蔽掉信息位。像SPARC这样的平台有内置硬件支持指针屏蔽因此它不是问题,但对于x86,ZGC团队使用了一个简洁的多映射技巧。操作系统

多重映射

要了解多映射的工做原理,咱们须要简要解释虚拟和物理内存之间的区别。物理内存是系统可用的实际内存,一般是安装的DRAM芯片的容量。虚拟内存是抽象,意味着应用程序有本身的(一般是隔离的)视图到物理内存。操做系统负责维护虚拟内存和物理内存范围之间的映射,它经过使用页表和处理器的内存管理单元(MMU)和转换后备缓冲区(TLB)来实现这一点,后者转换应用程序请求的地址。

多映射涉及将不一样范围的虚拟内存映射到同一物理内存。因为设计只有一个重映射,mark0和MARK-1能够在任什么时候间点为1,这是可能的三个映射作到这一点。ZGC源代码中有一个很好的图表

负载障碍

负载障碍是每当应用程序线程从堆加载引用时运行的代码片断(即访问对象上的非原始字段):

void printName( Person person ) {
    String name = person.name;  // would trigger the load barrier
                                // because we’ve loaded a reference 
                                // from the heap
    System.out.println(name);   // no load barrier directly
}
复制代码

在上面的代码中,分配名称的行包括跟随对堆上对象数据的person引用,而后将引用加载到它包含的名称。此时触发负载屏障。触发打印到屏幕的第二行不会直接致使加载障碍触发,由于没有来自堆的引用加载 - 名称是局部变量,所以没有从堆加载引用。可是,引用System和out,或者println内部可能会触发其余负载障碍。这与其余GC使用的写屏障造成对比,例如G1。加载屏障的工做是检查引用的状态,并在将引用(或者甚至是不一样的引用)返回给应用程序以前执行一些工做。在ZGC中,它经过测试加载的引用来执行此任务,以查看是否设置了某些位,具体取决于当前阶段。若是引用经过测试,则不执行任何其余工做,若是失败,则在将引用返回给应用程序以前执行某些特定于阶段的任务。

Marking

如今咱们了解了这两种技术是什么,让咱们看看ZGC GC循环。循环的第一部分是标记。标记涉及到以某种方式查找和标记全部堆对象,这些对象能够被正在运行的应用程序访问,换句话说,查找不是垃圾的对象。

ZGC的标记分为三个阶段。第一个阶段是Stop The World阶段,在这个阶段中,GC的根被标记为存在。GC根相似于局部变量,应用程序可使用它访问堆上的其余对象。若是对象不能经过从根开始的对象图遍从来访问,那么应用程序就没法访问它,它被认为是垃圾。根中可访问的对象集称为活动集。GC根标记步骤很是短,由于根的总数一般相对较小。

一旦该阶段完成,应用程序将继续,ZGC将开始下一个阶段,该阶段将同时遍历对象图并标记全部可访问对象。在此阶段,load barrier测试全部装载的引用,它将根据一个掩码测试它们是否已经被标记,若是一个引用尚未被标记,那么它将被添加到一个队列中以进行标记。

在遍历完成以后,会有一个最后的,简短的,Stop The World阶段,它处理一些边缘状况(咱们暂时忽略它),而后标记完成。

Relocation

GC循环的下一个主要部分是从新定位。重定位包括移动活动对象,以便释放堆的各个部分。为何要移动对象而不仅是填补空白?一些GCs确实这样作了,但它的不幸后果是,分配变得更加昂贵,由于当须要分配时,分配程序须要找到放置对象的空闲空间。相反,若是能够释放大量内存,那么分配就会简单地按照对象所需的内存数量递增(或“碰撞”)指针。

ZGC将堆划分为页面,在此阶段的开始,它会同时选择一组页面,这些页面的活动对象须要从新定位。当选择重定位集时,有一个Stop(Stop The World),ZGC将重定位重定位集中做为根(局部变量等)引用的任何对象,并将其引用从新映射到新位置。与前面的Stop the World步骤同样,这里所涉及的暂停时间仅取决于根的数量和重定位集的大小与活动对象的总大小的比例,而这一般是至关小的。它不像许多收集器那样根据堆的总体大小进行缩放。

在移动任何须要的根以后,下一个阶段是并发从新定位。在此阶段,GC线程遍历重定位集并从新定位它所包含的页面中的全部对象。应用程序线程也能够从新定位重定位集中的对象,若是它们试图在GC从新定位对象以前加载它们,那么这能够经过负载屏障(从堆中加载引用时触发)实现,详见如下流程图:

这可确保应用程序看到的全部引用都已更新,而且应用程序不可能对同时重定位的对象进行操做。

GC线程最终将重定位重定位集中的全部对象,可能仍有引用指向这些对象的旧位置。GC能够遍历对象图并从新映射全部对其新位置的引用,但这是一个昂贵的步骤。相反,这与下一个标记阶段相结合。在步行期间,若是发现未从新映射引用,则将其从新映射,而后标记为活动。

Recap

试图单独理解复杂垃圾收集器(如ZGC)的性能特征是很困难的,但从前面的部分能够清楚地看出,咱们所涵盖的几乎全部暂停都涉及依赖于GC根集合的工做,而不是实时的对象集,堆大小或垃圾。处理标记终止的标记阶段的最后一次暂停是一个例外,可是是增量的,若是超过期间预算直到再次尝试,GC将恢复为并发标记。

性能

那它是如何表现的?Stefan Karlsson和Per Liden在今年早些时候的Jfokus演讲中给出了一些初步数字。ZGC的SPECjbb 2015吞吐量数据与Parallel GC(优化吞吐量)大体至关,但平均暂停时间为1ms,最长为4ms。这与平均暂停时间超过200毫秒的G1和平行相反。

然而,垃圾收集器是复杂的野兽,基准测试结果可能没法推广到真实世界的性能。咱们期待本身测试ZGC,以了解它的性能如何因工做量而异。

相关文章
相关标签/搜索