《深入理解Java虚拟机》2.垃圾回收_垃圾收集算法

> > 垃圾收集算法

参考路飞大佬的博客

1、分代收集理论

当代商业虚拟机的垃圾收集器,大多数都遵循了"分代收集(Generational Collection)"的理论进行设计,分代收集名为理论,实质是一套符合大多数程序实际运行情况的经验法则,它建立在两个假说之上

  • 弱分代假说(Weak Generational Hypothesis) : 绝大多数对象都是朝生夕灭的
  • 强分代解说(Strong Generational Hypothesis) : 熬过越多次垃圾回收过程的对象就越难以消亡

这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则 收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储

在Java堆划分出不同的区域之后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域——因而才有了Minor GCMajor GCFull GC 这样的回收类型的划分。也才能针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了 "标记-复制算法" "标记-清除算法" "标记-整理算法"

四种GC的定义

部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为

  • 新生代收集**(Minor GC / Young GC)**: 指目标只是新生代的垃圾收集

  • 老年代收集**(Major GC / Old GC)** : 指目标只是老年代的垃圾收集,目前只有CMS收集器会有单独收集老年代的行为。

  • 混合收集**(Mixed GC)** 指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为

整堆收集(Full GC)

收集整个Java堆和方法区的垃圾回收

2、标记清除算法(老年代)

2.1概述及回收过程

标记清除算法是最早出现的也是最基础的垃圾收集算法,(之所以说它是最基础的收集算法,是因为后续的收集算法大多都是以标记-清除算法为基础,对其缺点进行改进而得到的) 在1960年由Lisp之父John-McCarthy提出。如它的名字一样,算法分为标记清除两部分,

1、首先标记出所有需要回收的对象(就是标记阶段的引用计数算法或者可达性分析算法)

2、标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未标记的对象。

注意:

所谓的清除并不是真正的将内存置空,而是把需要回收的内存地址保存在空闲的地址列表中,下次有新对象需要分配内存时如果空间直接进行覆盖。

图示

在这里插入图片描述

2.2缺点

1、执行效率不稳定。

如果Java堆中包含大量的对象,而且其中大部分是需要回收的,这时必须进行大量标记和清除动作,导致标记和清除两个过程的执行效率都随对象数量增长而减低,

2、内存空间的碎片化问题

标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。

3、复制算法(新生代)

为了解决标记-清除算法面对大量回收对象时执行效率低的问题,1969年Fenichel提出了一种"半区复制"的垃圾回收算法。

它将可用内存按照容量分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活的对象复制到另外一块上去,然后在将已使用过的内存空间一次性清理掉

图示

注意:这里在第一次将存活对象复制到另一块内存上时,实际上最后还有一步将两个内存交换的过程。

具体图示参考路飞大佬的博客

在这里插入图片描述

4、标记-整理算法(老年代)

复制算法在对象存活率较高时就要进行较多的赋值操作(所以复制算法一般用于新生代回收),效率将会降低。针对老年代对象(回收对象较少)的存亡特征,1974年Edward Lueders提出了另外一种有针对性的标记-整理算法

其中的标记过程仍然与标记清除算法一样,但后续的步骤不是直接对可回收对象进行清理,而是将所有存活的对象都向内存空间的一端移动,然后直接清理调边界以外的内存,有效的避免的内存碎片的问题,但是牵扯到对象的整理移动,需要消耗一定的时间,所以效率较低

标记清除算法和标记整理算法的本质区别在于:标记清除算法是一种非移动式的回收算法,标记整理算法是一种移动式的回收算法(移动过程中 STW)

图示

在这里插入图片描述

5、三种算法总结

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/121125929