JVM之GC篇:(二)垃圾回收算法

0x00 前言

本文我们将介绍几种常见的垃圾回收算法,了解它们的优缺点,并尝试在这些算法上进行改进得到更好的GC算法。

在这里插入图片描述

0x01 GC算法的评价指标

  1. 最大暂停时间。在相同的总暂停时间下,最大暂停时间越大,则用户程序中断的时间越久,而对于用户而言,他们的耐心显然是有一个阈值,如果最大暂停时间超过了这个阈值,那么就会带来极差的用户体验。因此一个好的GC算法应该尽可能减少每次STW(stop the world)时间。
  2. 吞吐量。即GC时间占用程序总运行时间的比值,显然我们希望GC时间占用的越少越好,尽可能多地运行用户代码。
  3. 堆使用效率。标记-清除算法不需要使用额外的堆空间,因此留给用户程序的内存更多,而复制算法则需要使用一半的堆空间。因此前者的堆使用效率更高。我们希望GC算法占用的额外堆空间越少越好。

通常来说,这三者不可能全部达成,即我们很难获得一个最大暂停时间短,吞吐量高,堆使用效率高的GC算法。我们需要根据实际的业务场景去使用最适合的GC算法。

简单解释一下为什么不太可能同时获得三者,因为最大暂停时间、吞吐量与算法的时间复杂度有关,而堆使用效率与算法的空间复杂度有关,我们知道通常需要牺牲空间去换取时间,或者反之去牺牲时间来换取空间,所以二者很难兼得。

0x02 标记-清除算法

  1. 标记阶段:从GC root对象出发,根据可达性分析,给对象打上标记。
  2. 清除阶段:将没有标记的对象回收。

其优点在于实现简单,堆内存效率高,而缺点则是(1)容易产生内存碎片,JVM需要维护一个空闲链表用于堆内存的分配(2)每次分配内存需要遍历空闲链表,直到找到合适大小的空闲块,时间复杂度为O(n)。

在这里插入图片描述

0x03 复制算法

堆分为from空间和to空间,对象在from空间上进行分配。

  1. GC开始时,将根对象从from空间移动到to空间
  2. 从根对象开始遍历,采用追加的方式将引用到的对象复制到to空间
  3. 清理from空间,交换from空间和to空间的名称

缺点显而易见,对象分配只能使用一半的堆内存,同时复制对象增加了额外的时间开销。优点在于追加的方式不会产生内存碎片。

0x04 标记-整理算法

标记-整理算法解决了标记-清除算法的内存碎片问题,主要步骤包括

  1. 标记对象
  2. 将所有标记对象移动到内存数组的前半部分,留出后半部分
  3. 清理后半部分的内存
    其优点在于高效的堆内存使用以及无内存碎片产生,且分配内存是可以采用高效的追加方式,然而整理的过程涉及到了对象的移动,因此时间上复杂度更高。

在这里插入图片描述

0x05 分代算法

分代算法的思想是将对象按存活时间进行分类,新产生的对象会被放入新生代,而在多轮GC中存活的对象被认为是长期存在的对象,会被放入老年代。新生代发生GC的频率会高于老年代。

新生代进一步可分为Eden区、S-from区和S-to区。新创建的对象会被分配到Eden区,当Eden区满,则触发Young GC,使用复制算法进行回收,Eden区和S-from区的对象被复制到S-to区,并且增加每轮GC存活下的对象年龄(从0开始),当一个对象年龄大于等于某个值(默认15,具体取决于垃圾回收器),会将其从新生代转移到老年代。

并不是所有老年代的对象都是长期存活的对象,当新生代无法满且YGC无法回收这些对象时,会将这些对象放入到老年代。因此当老年代满时,会优先对新生代进行一次YGC,如果YGC无法回收则会对整个堆进行一次Full GC,Full GC的STW时间更长,频率也更低。

为什么要分代呢?目的在于更加精细化的GC策略。对于新对象,被回收的概率大,且大部分是小对象,使用复制算法不会浪费过多的内存,并且效率上有一定保证;对于老对象,一直存在的概率大,且大部分是大对象,则使用标记-清除/整理算法更适合。

在这里插入图片描述

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值