JVM 垃圾回收相关算法介绍及图示

630 阅读7分钟

概要:当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。目前在JVM中比较常见的三种垃圾收集算法是:标记一清除算法(Mark-Sweep)复制算法(copying)标记-压缩算法(Mark-Compact)

封面地点:湖南省永州市蓝山县舜河村

作者:用心笑*😀

一、标记-清除算法

标记-清扫式垃圾回收器是一种直接的全面停顿算法。简单的说,它们找出所有不可达的对象,并将它们放入空闲列表Free。

标记:

标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。

如下图:

在这里插入图片描述

清除:

清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

在这里插入图片描述

这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放覆盖原有的地址。

  • 如果内存规整
    • 采用指针碰撞的方式进行内存分配
  • 如果内存不规整
    • 虚拟机需要维护一个列表
    • 空闲列表分配

优点:优点就是简单理解,容易实现。内存消耗较小。

缺点:1)需要扫描整个堆区,时间开销较大,效率不算高;2) 进行GC 时,会停止用户程序,而又因为时间开销大,所以对用户体验较差;3) 此方式清理出来的空间内存,可能是不连续,这就可能会产生内碎片(操作系统方面知识),就需要再维护一个空列表。

二、标记-复制算法

2.1、概述:

为了解决标记 - 清除算法在垃圾收集效率方而的缺陷M.L.Minsky于1963 年发表了著名的论文, “ 使用双存储区的Lisp语言垃圾收集器CA LISP Garbage CoIIector Algorithm Using SeriaI Secondary Storage 。M.L.Minsky 在该论文中描述的算法被人们称为复制(copying) 算法, 它也被M.L.Minsky 本人成功地引入到了Lisp语言的一个实现版本中。

GC复制算法原理是把内存分为两个空间一个是From空间,一个是To空间,对象一开始只在From空间分配,To空间是空闲的。GC时把存活的对象从From空间复制粘贴到To空间,之后把To空间变成新的From空间,原来的From空间变成To空间。回收前后对比下图所示

2.2、图示

在这里插入图片描述

应用场景:目前JVM大都使用在新生代的垃圾回收中。

2.3、优缺点

对于任意的GC算法我们都可以从吞吐量(GC延迟),内存的分配效率,内存碎片化,堆的使用效率这个几个方面评估。

优点
  • GC复制算法只搜索并复制存活的对象,少了访问整堆和构造空闲链表的操作能够在短时间内完成GC。换言之,GC复制的吞吐量要比标记-清除要优秀,并且堆越大这种差距越明显。

  • 内存的分配效率:GC复制算法不使用空闲链表,因为分块本身就是一个连续的空间。只要新建的对象不超过剩余空间的大小,只需要移动指针即可~。所以GC复制算法的分配效率非常的高效

  • 复制过去以后保证空间的连续性,不会出现“碎片”问题。

缺点
  • 此算法的缺点也是很明显的,就是需要两倍的内存空间。
  • 复制而不是移动,意味着GC需要维护两块内存之间对象引用关系,不管是内存占用或者时间开销也不小

2.4、注意

复制算法还有一个情况:如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,或者说非常低才行(老年代大量的对象存活,那么复制的对象将会有很多,效率会很低),所以复制算法大都是应用在新生代中的垃圾回收。

三、标记-整理算法(标记-压缩算法)

3.1、概述

复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的,这种情况在新生代经常发生,老年代很难出现这种情况。

标记一清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下,而且在执行完内存回收后还会产生内存碎片,所以JVM的设计者认为前两种都有问题,即提出了第三种垃圾收集算法:标记-整理算法,可以说是整合了前两种的优势。

前面标记部分同标记清除部分,不同的地方在于,标记清除中只是把可回收的对象进行垃圾回收,不会对剩余的内存空间进行整理,而标记整理则会对存活的对象进行整理,截图对比如下所示:

3.2、标记-清除与标记整理算法区别

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。

二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。

3.3、优缺点

优点: 则是一定程度上解决了前两种算法的缺陷,1)消除了标记-清除算法当中,内存区域分散的缺点;2)不用像复制算法一样,内存减半的高额代价。

缺点:

1)从效率上讲,是不如复制算法的。

2)移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址

3)移动过程中,需要全程暂停用户应用程序。

3.4、小结

标记清除标记整理复制
速率中等最慢最快
空间开销少(但会堆积碎片)少(不堆积碎片)通常需要活对象的2倍空间(不堆积碎片)
移动对象

结论:没有最好的算法,只有最合适的算法。(不然那还有这么多算法的存在勒)

堆部分,年轻代是采用复制算法进行垃圾回收,因为年轻代一般都是存活时间不长的对象,在第一次进行垃圾回收的时候,会把大部分的对象清除掉,这种情况下使用复制算法,只需要把少量存活的对象放入到另一块闲置的内存块中即可。

而老年代中,一般对象的存活比例会很高,这种情况下,使用复制算法不能很好的提高性能和效率,把存活的对象移到另一个内存块时,会由于对象存活多而消耗的时间多,从而影响效率,这种情况下,使用标记整理或者标记清除比较合适。

四、自言自语

每天的生活,慢慢变的充实,未来很长,我们一起努力!!!