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垃圾回收器详解:串行回收,不同的复制算法比较及对程序员的启迪

相关文章
|
6月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
654 55
|
11月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
300 27
|
9月前
|
人工智能 编解码 算法
DeepSeek加持的通义灵码2.0 AI程序员实战案例:助力嵌入式开发中的算法生成革新
本文介绍了通义灵码2.0 AI程序员在嵌入式开发中的实战应用。通过安装VS Code插件并登录阿里云账号,用户可切换至DeepSeek V3模型,利用其强大的代码生成能力。实战案例中,AI程序员根据自然语言描述快速生成了C语言的base64编解码算法,包括源代码、头文件、测试代码和CMake编译脚本。即使在编译错误和需求迭代的情况下,AI程序员也能迅速分析问题并修复代码,最终成功实现功能。作者认为,通义灵码2.0显著提升了开发效率,打破了编程语言限制,是AI编程从辅助工具向工程级协同开发转变的重要标志,值得开发者广泛使用。
8727 71
DeepSeek加持的通义灵码2.0 AI程序员实战案例:助力嵌入式开发中的算法生成革新
|
6月前
|
存储 监控 算法
Java程序员必学:JVM架构完全解读
Java 虚拟机(JVM)是 Java 编程的核心,深入理解其架构对开发者意义重大。本文详细解读 JVM 架构,涵盖类加载器子系统、运行时数据区等核心组件,剖析类加载机制,包括加载阶段、双亲委派模型等内容。阐述内存管理原理,介绍垃圾回收算法与常见回收器,并结合案例讲解调优策略。还分享 JVM 性能瓶颈识别与调优方法,分析 Java 语言特性对性能的影响,给出数据结构选择、I/O 操作及并发同步处理的优化技巧,同时探讨 JVM 安全模型与错误处理机制,助力开发者提升编程能力与程序性能。
Java程序员必学:JVM架构完全解读
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
8月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
6月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
192 0
|
6月前
|
算法 Java 对象存储
JVM深入原理(八)(二):垃圾回收
Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为StopTheWorld简称STW,如果STW时间过长则会影响用户的使用。一般来说,堆内存越大,最大STW就越长,想减少最大STW,就会减少吞吐量,不同的GC算法适用于不同的场景。分代回收算法将整个堆中的区域划分为新生代和老年代。--超过新生代大小的大对象会直接晋升到老年代。
165 0
|
8月前
|
监控 算法 Java
JVM—垃圾收集算法和HotSpot算法实现细节
JVM的垃圾收集算法和HotSpot的实现细节复杂但至关重要,通过理解和掌握这些算法,可以为Java应用程序选择合适的垃圾收集器,并进行有效的性能调优。选择适当的垃圾收集策略,结合合理的内存配置和日志分析,能够显著提升应用的运行效率和稳定性。
185 15
|
12月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
278 28

热门文章

最新文章