早在三年前,Red Hat 就启动了 Shenandoah 项目。Shenandoah 是一种新的 Java 虚拟机 GC 算法,目标是利用现代多核 CPU 的优势,减少大堆内存在 GC 方面存在的停顿时间。Shenandoah 后来被贡献给了 OpenJDK,正式成为 OpenJDK 的开源项目,也就是 JEP 189 。
总的来说,大部分垃圾回收器要么使用古老的标记并清除算法,要么使用分代算法,再结合并发和堆压缩。垃圾回收器多种多样,但没有一种回收器能够满足所有的需求,它们总要在某些方面做出折衷,并且不可避免地存在停顿时间。而 Shenandoah 最大的两个特点是它伸缩性和超低停顿时间。
Shenandoah 最初的目标是把 GC 停顿时间降到 10 毫秒以下,并且对内存的支持扩展到 TB 级别。为了降低停顿时间,回收器需要使用更多的线程来并行处理回收任务。而要在降低停顿时间的同时能够支持更大的堆空间,回收器对 CPU 的多核处理能力提出了更高的要求。相比于 CMS 和 G1 ,Shenandoah 不仅进行并行的垃圾标记,在压缩堆空间时也是并行进行的。
Shenandoah 把堆空间分为很多区域,例如整个堆空间是 1G,如果每个区域是 1M,那么就会有 1000 多个区域。传统的标记并清除回收器并没有区域的概念,而拷贝回收器一般也只有两个或少数几个区域。通过更细粒度的分区,Shenandoah 可以优先对包含更多垃圾的区域进行回收,同时有助于并行回收工作的进行。
Shenandoah 是一个标记拷贝回收器,它的回收工作分为两个阶段。第一个阶段是标记阶段。在这个阶段,回收器会对每个区域里的对象进行标记,并计算它们的数量。第二个阶段,回收器对源区域的对象进行扫描,并把存活对象拷贝到目标区域,然后源区域的内存就可以被释放。这两个阶段看似很简单,但要让整个过程并行进行,从而降低停顿时间,事情就会变得复杂很多。
到目前为止,Shenandoah 已经实现了很多特性,包括运行时、解释器、C1 屏障和 C2 屏障、对弱引用的支持、对 JNI 临界区域的支持、对 System.gc() 的支持,等等。Shenandoah 目前还算稳定,它的平均性能能够达到 G1 的 90%,有时候会差一些,比如 70%,不过有时候会超过 G1,比如 150%。不过 Shenandoah 的停顿时间比 G1 要短很多,不过相比之前定下的目标,还有很大距离。
目前还没有把 Shenandoah 用在 Java 9 里的计划,不过如果有人对此感兴趣,可以自己从源代码构建特别版本的JDK 来体验Shenandoah。关于更多Shenandoah 的细节可以在这里看到。
不过,从Shenandoah 的目标来看,它更适合用在大堆上。所以,如果CPU 资源有限,内存也不大,比如小于20G,那么就没有必要使用Shenandoah。
感谢郭蕾对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论