JVM垃圾回收器详解:不同的复制算法比较及对程序员的启迪

简介: 前面提到整个JVM中只有串行回收按照Cheney的设计实现新生代回收,其他的垃圾回收器在新生代回收时都对Cheney的复制算法进行了增强。其中最大的改变就是不使用宽度优先,而是使用深度优先的处理方式。其中Moon在1984年提出了一种近似深度优先遍历的处理方式,称为层次遍历,使用层次遍历大概可以将GC效果提升6%。

不同的复制算法比较及对程序员的启迪

前面提到整个JVM中只有串行回收按照Cheney的设计实现新生代回收,其他的垃圾回收器在新生代回收时都对Cheney的复制算法进行了增强。

其中最大的改变就是不使用宽度优先,而是使用深度优先的处理方式。其中Moon在1984年提出了一种近似深度优先遍历的处理方式,称为层次遍历,使用层次遍历大概可以将GC效果提升6%。

研究发现,宽度优先导致的性能问题在于数据局部性,这会导致数据访问缓存命中率下降。

那为什么宽度优先的复制算法会导致垃圾回收后应用运行时存在数据局部性问题呢?根本原因在于应用中对象的访问模型。测试结果表明,大多数应用在访问对象时同时访问父对象和子对象的概率更高,而同时访问两个兄弟对象的概率更低。也就说应用中对象的访问从统计角度更多的是按照深度优先进行,而不是按照宽度优先进行的。

这样的结果导致使用宽度优先复制算法在对象重排以后和应用中对象的访问模型并不一致,更容易导致缓存不命中,从而导致性能下降。

从这个角度出发,如果决定使用串行垃圾回收时,在开发应用的时候对象的访问尽量遵循宽度优先的访问方式;如果决定使用其他垃圾回收器,在开发应用的时候对象尽量采用深度优先的访问方式。这样的做法就能保证应用中对象的访问方式在垃圾回收执行完成之后和对象的组织方式一致,从而取得更好的运行效果。

最后需要指出的是,采用深度优先遍历的实现通常需要一个辅助空间栈(Stack)。但使用辅助栈会带来额外的问题,那就是栈空间应该多大?过大会导致空间浪费,太小则导致标记过程栈溢出。所以需要一种合理的设计,既不浪费空间,又能在栈溢出时正确处理。那么JVM是如何解决这个问题的呢?读者可以先思考一下这个问题。

虽然JVM中并没有采用Moon算法实现分代的复制,但是在另一款开源的JVM产品OpenJ9中,分代回收的复制算法优先使用的就是并行的Moon算法。

这里简单介绍一下Moon的算法,由于Moon算法是Cheney算法的优化,因此我们先回顾一下Cheney的算法思想,再来看看Moon是如何优化的。

Cheney算法可以简单总结为:在往目标空间复制对象的时候,额外引入了两个指针,分别为Scan和Free,Scan和Free直接在内存空间中模拟了队列,队列中存放的是待处理的对象,从Scan开始遍历直到Scan和Free重合,对象就全部处理完成。如图3-38所示,分配空间用浅蓝色表示,To空间中使用3种颜色描述对象处理的状态。

网络异常,图片无法展示
|

图3-38 Cheney算法示意图

Moon算法的改进主要包含如下几个要点:

1)将目标空间划分为更细粒度的块,如图3-39中的A、B、C、D、E。

2)在标记/复制的时候,总是从最后一个尚未填满的块开始,如图3-39中的块D会被先处理,所以使用了一个PrimaryScan表示扫描先从这里开始。

当PrimaryScan和Free之间的对象标记/复制完成之后(注意,这里仍然采用宽度优先遍历的方式进行处理),如果块D的剩余空间不能满足对象的填充,那么块E会被使用。

3)当块D被填满之后,再按照SecondaryScan指向的位置开始再次进行标记/复制。当再次出现了未填满的块时,则按照上一步中的方法继续优先处理。

从这些描述可以看出,Moon算法最大的一个缺点是有些对象可能需要扫描2次才能完成。例如图中的D填充满了之后,从块B开始扫描,然后再扫描块C(实际上块C的对象在第一次遇到未填满的时候已经进行了一次扫描)。算法的示意图如图3-39所示。

网络异常,图片无法展示
|

图3-39 Moon算法示意图

使用Moon算法的效果如何?这里采用论文中的示例来演示一下,如图3-40所示。

  • 宽度优先算法,得到对象的存储顺序为O1、O2、……、O14、O15。
  • 深度优先复制算法,得到对象的存储顺序为O1、O2、O4、O8、O9、
  • O5、O10、O11、O3、O6、O12、O13、O7、O14、O15。

使用Moon修正的算法,假设每个块只能保存3个对象,得到对象的存储顺序为O1、O2、O3、O4、O8、O9、O5、O10、O11、O6、O12、O13、O7、O14、O15。

网络异常,图片无法展示
|

图3-40 Moon算法示例

结论是,深度优先得到的对象存储序列和Moon的修正算法仅仅只有O3的位置有所不同,所以Moon算法也被称为近似的深度优先复制算法。同时也要注意,块大小影响最终的结果。在本例中块大小设置为保存3个对象,最后的结果和深度优先复制算法类似。但是当块大小设置得更大一些,结果就可能与深度优先差别比较大了。

例如块的大小设置为保持5个对象,此时得到对象的存储顺序为:O1、O2、O3、O4、O5、O6、O12、O13、O7、O14、O15、O8、

O9、O10、O11。可以看到,此时对象的存储顺序与宽度优先遍历更为接近(假设以对象在相同位置为标准)。再比如设置块大小为保存15个对象,此时Moon就完全退化为宽度优先复制算法了。从这里可以看出这个算法的关键是为块设置一个合适的大小。

另外,Moon是一个串行的算法,Siegwart在2006年将Moon算法修改成并行算法。OpenJ9中层次遍历就是基于这篇论文实现的。由于并行算法涉及任务切分、任务均衡等细节,本章主要关注串行算法的实现,因此不对OpenJ9中的实现展开介绍。

在这里,我们谈论了宽度优先遍历、层次遍历和深度优先遍历。研究表明,层次遍历的效果比宽度优先遍历好。那么层次遍历和深度优先遍历的效果相比如何?

2018年,OpenJ9社区发起了一个针对层次遍历的优化,为OpenJ9中增加一款基于深度优先遍历的并行实现。

这里仅仅介绍一下优化改进后的效果,其作者针对3种应用分别使用层次遍历和深度优先遍历进行了测试,从测试结果来看在吞吐量和停顿时间上,基于深度优先遍历的回收算法都有明显的优势。测试选择的3种应用是:交易型应用(Transactional)、数据处理型应用(Data Compilation)和数据库应用(Database Processing)。测试中使用OpenJ9的gencon垃圾回收器,该垃圾回收器的新生代既支持宽度优先遍历又支持层次遍历,默认使用层次遍历。作者在测试中指明了测试使用的OS、内存大小等信息,对测试过程感兴趣的读者可以阅读原文。深度优先和层次优先遍历的性能测试对比结果如表3-4所示。

网络异常,图片无法展示
|

表3-4 深度优先和层次遍历优先性能测试结果

针对3种应用,GC的吞吐量分别提升22.671%、20.864%和2.805%;GC的停顿时间分别减少18.01%、16.074%和3.113%。另外,原文中还给出了测试套跑分的提升,但比较遗憾的是作者并未给出测试套的更多信息,所以本书并未列出。另外,需要注意的是,该优化最终并未合入OpenJ9的主线版本(可能与OpenJ9的技术路线有关)。

最后做一个简单的总结:从目前的论文研究和测试结果来看,使用深度优先遍历的回收算法效果最好,层次遍历次之,宽度优先遍历最差;深度优先遍历在实现时有额外的内存空间开销,而层次遍历和宽度优先遍历没有。

对于程序员来说,应了解不同产品中采用的算法以及算法本身的使用场景,在开发应用时结合选择的回收算法,使用深度优先的方式或者宽度优先的方式来访问对象,可以获得额外的收益。

本文给大家讲解的内容是JVM垃圾回收器详解:串行回收,不同的复制算法比较及对程序员的启迪

相关文章
|
2月前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
40 0
|
9天前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
1月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
2月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
53 1
|
2月前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
46 5
|
2月前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
2月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
2月前
|
监控 Java 测试技术
Elasticsearch集群JVM调优垃圾回收器的选择
Elasticsearch集群JVM调优垃圾回收器的选择
59 1
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
107 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS