引用计数逻辑简单,可以做到对无用的资源进行立即回收,但无法应对高并发、高吞吐的场景,另外循环引用问题是其致命缺陷。
Mark-Sweep 算法回收的是垃圾对象,如果垃圾对象比较少,回收阶段所做的事情就比较少,所以它适合于存活对象多、垃圾对象少的情况。
标记位图与写时复制技术兼容,清除操作更高效。
复制算法搬移的是活跃对象,所以它更适合存活对象少、垃圾对象多的情况。
关键变量:
核心思想:在每个页面上分别进行广度优先搜索
执行结果如下图所示:
在 GC 标记-压缩算法中对象的新空间和原空间可能会有重合,因此在移动对象前,需要事先将各对象的指针全部更新到预计要移动到的地址。
在这个步骤中需要搜索整个堆,更新各个对象的指针。请注意,这是第 2 次对整个堆执行搜索。
在这个步骤中需要搜索整个堆,将活动对象移动到forwarding指针的引用目标处。请注意,这是第 3 次搜索整个堆。
分代GC这个概念最早由 David Ungar 于1984年在论文中提出的,他将堆内存分为4个空间:Eden、两个Survivor、OldGen,其中Eden和两个Survivor合称为新生代空间。堆结构如下图所示:
核心思想:对于存活时间比较短的对象,用复制算法回收;对于存活时间比较长的对象,就可以使用 Mark-Sweep 算法。
记录集(Remember Set)被用于高效地寻找跨代引用,具体来说,在 GC 时将记录集看成根(像根一样的东西),并进行搜索,以发现指向待回收区的指针。
最直观的思路是,记录集里记录被引用到的对象:
上图左边的记录集中记下了被跨代引用的 B 和 D 两个对象,那么当发生年轻代 GC 时,就能正确地找到 B 和 D 对象是根对象。但是这样做的问题是,当 B 和 D 再经过一轮年轻代 GC,它们的位置发生变化以后,A 对象对它们的引用无法正确维护。
为了解决这个问题,在记录集里不会记录引用的目标对象,而是记录发出引用的对象:
写屏障(write barrier)是维护记录集的手段:
对象晋升也可能产生跨代引用:
卡表(Card table)可以提升写屏障效率,借鉴了位图的思路。
只更新脏卡片:
三色标记的问题:
第 46 和 47 位是预留的,也就是说标记位可以继续向左移两位,那么可以支持的堆空间就可以扩展到 16T。
上图中,当 foo 对象发生转移之后,对象 a 再访问 foo 时就会触发 read barrier。read barrier 会查找 forwarding table
来确定对象是否发生了转移,确定 foo 被转移到新地址 foo(new)之后,直接将这一次对 foo 的访问更改为 foo(new)。由于整个过程是依托于 read barrier 自动完成的,这个过程也叫“自愈”。
读屏障示例: