searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

常见Java垃圾回收(GC)算法及垃圾回收器分析

2024-05-29 09:08:34
1
0

Java的垃圾回收(Garbage Collection, GC)机制是Java虚拟机(JVM)自动管理内存的重要组成部分,它自动追踪对象的生命周期,自动释放不再使用的对象所占用的内存空间,从而减轻了开发人员手动管理内存的负担。Java的GC主要涉及几种不同的垃圾回收算法,每种算法都有其特定的设计目标、适用场景以及优缺点。下面是对几种主要垃圾回收算法的总结:

垃圾回收算法

1. 标记-清除算法(Mark-Sweep)

原理

  • 标记阶段:遍历所有的可达对象,将它们标记为存活。

  • 清除阶段:遍历堆内存,删除那些未被标记的对象,即未被引用的对象。

优点

  • 实现简单,是最早的垃圾回收算法之一。

  • 直观地解决了内存管理问题,不需要对象的移动。

缺点

  • 效率问题:标记和清除两个过程的效率都不高,特别是清除后会产生大量不连续的内存碎片,影响后续的大对象分配。

  • 空间碎片:由于不进行内存整理,会造成内存空间碎片化,可能导致后续分配大对象时,即使有足够的空闲内存空间,也无法满足分配需求。

GC标记-清除法的改进

改进一:分配速度的改进——多个空闲链表

利用分块大小不同的空闲链表,即创建只连接大分块的空闲链表和只连接小分块的空闲链表,甚至不同规格大小的分块采用不同的空闲链表管理。这样一来,只要按照应用程序所申请的对象大小选择空闲链表,就能在短时间内找到符合条件的分块了。我们知道,Golang的内存分配里就是这么做的了。

改进二:碎片化分块问题的改进——BiBOP法

BiBOP 是 Big Bag Of Pages 的缩写。用一句话概括就是“将大小相近的对象整理成固定大小的块进行管理的做法”。

 

2. 复制算法(Copying)

原理

  • 将可用内存分为两块,每次只使用其中一块。当一块用完时,将存活的对象复制到另一块,然后清理掉原来的那块内存。

优点

  • 简化了内存管理,解决了标记-清除算法的空间碎片问题,因为每次回收后都是连续的内存。

  • 复制过程相对高效,因为是连续内存区域之间的复制。

缺点

  • 内存利用率低:始终有一半的内存处于闲置状态。

  • 对象存活率高时效率下降:如果对象的存活率很高,频繁的复制操作会降低效率。

改进一:Cheney 的 GC 复制算法

将基本GC的深度优先搜索改为广度优先搜索。这样可以将递归复制改为迭代复制。

改进二:多空间复制算法

GC 复制算法最大的缺点是只能利用半个堆。这是因为该算法将整个堆分成了两半,每次都要腾出一半。多空间复制算法可以改进GC复制算法“只能利用半个堆”的问题。

多空间复制算法说白了就是把堆 N 等分,对其中 2 块空间执行 GC 复制算法,对剩下的 (N-2)块空间执行 GC 标记-清除算法,也就是把这 2 种算法组合起来使用。

 

3. 标记-整理(Mark-Compact)

原理

  • 结合了标记-清除算法的标记过程,但在清除阶段,不是直接清理死亡对象,而是将存活对象向一端移动,然后直接清理掉边界外的所有空间。

优点

  • 解决了标记-清除算法的空间碎片问题,同时不需要像复制算法那样浪费一半的内存。

  • 适合老年代,因为老年代的对象生命周期较长,移动的频率较低。

缺点

  • 移动对象需要调整引用,比单纯的清除要慢。

  • 整理过程复杂,暂停时间较长,影响应用响应。

4. 分代收集算法(Generational Collection)

原理

  • 基于这样一个事实:大多数对象的生命周期很短。因此,将堆内存分为年轻代(Young Generation)和老年代(Old/Tenured Generation),分别采用不同的收集算法。

  • 年轻代常采用复制算法,因为它能快速处理大量短期对象。

  • 老年代通常使用标记-清除或标记-整理,因为这里的对象生命周期较长,不适合频繁复制。

优点

  • 针对不同对象生命周期采取不同策略,提高了整体的回收效率。

  • 减少了暂停时间,提高了应用程序的响应性。

缺点

  • 实现复杂,需要维护多个代的内存空间,以及相应的回收策略。

  • 对象晋升到老年代的决策可能影响整体性能。

5. 增量收集(Incremental GC)和并发收集(Concurrent GC)

增量收集

  • 将一次完整的垃圾回收过程分成多个小步骤,每次回收一小部分,减少因GC造成的停顿时间。

并发收集

  • 在用户线程运行的同时进行垃圾回收,尽量减少垃圾回收对应用的影响,比如CMS(Concurrent Mark and Sweep)和G1(Garbage First)收集器。

优点

  • 显著降低了GC引起的停顿时间,提高了应用程序的响应性和用户体验。

缺点

  • 实现复杂,可能导致内存回收效率降低,尤其是并发收集可能会出现“浮动垃圾”问题,需要多次回收才能彻底清理。

  • CMS和G1虽然并发,但在某些阶段仍需STW(Stop-The-World)操作,影响应用性能。

增量式垃圾回收的改进

改进一:Steele的写入屏障

具体操作:在标记过程中发出引用的对象是黑色对象,且新的引用的目标对象为灰色或白色,那么我们就把发出引用的对象涂成灰色。

改进二:删除屏障

具体操作: 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。

 

垃圾回收器及其关联的算法

 

不同的垃圾回收器可能支持不同的算法,并且可以选择和配置。以下是一些常见垃圾回收器及其关联的算法:

  1. Serial GC

  • 算法:标记-清除

  • 描述:这是最简单的垃圾回收器,适用于单线程环境。在新生代和老年代都可以使用,但通常新生代使用较多。它会停止所有应用线程(Stop-The-World)来执行垃圾回收。

  • 默认使用场景:客户端模式下的JVM默认垃圾回收器。

  1. Parallel GC(也称作Parallel Scavenge + Parallel Old):

  • 算法:新生代采用复制算法,老年代采用标记-整理或标记-清除。

  • 描述:这是一个多线程的垃圾回收器,可以在新生代和老年代并行执行垃圾回收,减少Stop-The-World的时间。

  • 默认使用场景:Java 8及以前的服务器模式下默认的垃圾回收器。

  1. Concurrent Mark Sweep (CMS)

  • 算法:标记-清除

  • 描述:CMS回收器主要关注减少应用的暂停时间,它在老年代使用,大部分工作可以在应用线程运行时并发进行,但最终的清理阶段仍需Stop-The-World。

  • 使用场景:适合对响应时间敏感的应用,需手动配置启用。

  1. G1(Garbage First)

  • 算法:混合使用标记-复制和标记-整理,分区算法。

  • 描述:G1设计目标是最大化可预测的垃圾回收暂停时间,同时保持高吞吐量。它将堆划分为多个区域(region),并使用复制和标记-整理的混合策略。

  • 默认使用场景:Java 9及以上版本中成为默认的服务器模式垃圾回收器。

默认算法

  • 在Java 8及之前的版本中,服务器模式下默认的垃圾回收器通常是Parallel GC(新生代使用复制算法,老年代使用标记-清除或标记-整理)。

  • 从Java 9开始,G1逐渐成为默认的垃圾回收器,其在新生代和老年代都使用了分区的标记-复制和标记-整理算法,以实现更可预测的暂停时间和高效回收。

请注意,这些信息是基于历史版本的普遍情况,具体版本的默认配置可能会随JVM实现和更新而变化。开发者可以通过JVM选项(如-XX:+UseSerialGC, -XX:+UseParallelGC, -XX:+UseConcMarkSweepGC, -XX:+UseG1GC)来指定垃圾回收器。

总结

Java的垃圾回收机制通过不断演进的算法设计,平衡了内存管理的效率、系统响应性和内存利用率。每种算法都有其应用场景,没有绝对的好坏,关键在于如何根据具体应用的特点选择合适的垃圾回收策略。现代JVM通常结合多种算法,如G1收集器,综合运用分代收集、标记-整理、并发标记等策略,以达到更好的性能表现。随着JVM技术的发展,垃圾回收算法还在不断进化,以适应更高性能要求和更大规模的应用场景。

 

0条评论
0 / 1000
magee
2文章数
0粉丝数
magee
2 文章 | 0 粉丝