标记清除算法
流程
标记
找到内存中需要回收的对象,并且把他们标记出来。
清除
清除掉被标记的对象,释放内存空间。
缺点
- 标记和清除都是比较耗时的过程,效率不高。
- 标记清除之后会产生大量的不连续内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存,从而不得不提触发另外一次垃圾收集动作。
标记清楚算法衍生规则只分配(动态分区分配策略)
首次适应算法(Fisrt-fit)
在遍历空闲链表的时候一旦发现有大小等于需要的大小时,立即把该块内存分配给对象,并立即返回。
最佳适应算法(Best-fit)
遍历空闲链表时,返回刚好等于需要大小的块。
最差适应算法(Worst-fit)
在遍历空闲链表时,找到空闲链表中最大的分块,将其分割给申请的对象,其目的就是使得分割后分块的最大化,以便下次好分配,不过这种分配算法很容易产生很多小的分块,这些分块也时不能使用的。
复制算法
流程
将内存划分为两块相等的区域,每次只使用其中一块。当其中一块内存使用完了,就将还存活的对象复制到另外一块中,然后把已经使用过的内存空间一次清除。
标记
将存活的对象进行标记
复制
将被标记的对象复制到另外一块区域
清除
清理掉被使用过的区域
缺点
空间利用率低
标记整理(压缩)算法
标记整理算法严格意义上应该叫“标记清除整理算法”或者“标记清除压缩算法”,因为它本质就是在标记清除的基础上再进行整理。
流程
标记
标记存活的对象
清除
将未被标记的对象清理掉
整理
将存活的对象移动到连续的空间中
优点
解决了空间碎片化问题,提高了空间利用率。
缺点
整个过程效率很低,整理时会移动对象并且还会改变对象的引用地址。
整理(压缩)算法分类
随机整理
对象的移动方式和它们的初始的对象排列及引用关系无关。
任意顺序整理实现简单,且执行速度快,但任意顺序可能会将原本相邻的对象打乱到不同的高速缓存行或者是虚拟内存页中(理解为打乱到内存各个位置),会降低赋值器的局部性。包括他只能处理固定大小的对象,一旦对象大小不固定,就会增加其他的逻辑。
典型的随机整理算法:双指针整理算法
实现简单且速度快,但会打乱对象的原有布局,属于随机整理。
整理前:两根指针分别位于内存的首尾段
第一次遍历:移动位置但并不更新标记
第二次遍历:更新标记
线性顺序
将具有关联关系的对象排列在一起。(现代垃圾回收很少使用,了解即可)
相关的对象会进行整理,整理成一块块小区域,无法避免内存碎片。
滑动顺序
将对象“滑动”到堆的一端,从而“挤出”垃圾,可以保持对象在堆中原有的顺序。
典型的滑动整理算法:Lisp2算法
需要将对象头用一个额外的槽来保存迁移完的地址
整理前:此算法时一个三指针算法,可以处理不同大小的对象。但是需要三次遍历,并且由于对象大小不一样,所以需要额外的空间存储,而不是直接移动。
第一次遍历:Free指针负责记录预留空间,Scan指针标记存活对象,End指针用来做指针碰撞的,当Scan或者Free指针与End指针相遇时,可以让Scan或者Free指针停止。Scan找到对象,free就会往前移动该对象对应大小的单位,最终free停留的位置,就是刚好足够所有存活对象存储的截止位置。
第二次遍历:更新对象的地址(修改GC Root的引用关系)
第三次遍历:移动对象
缺点:需要遍历三次,并且需要用对象额外的空间记录将要移动的位置,并且移动时也是会覆盖原来的对象。
引线整理算法
可以在不引入额外空间开销的情况下实现滑动整理,但需要2次遍历对且遍历成本较高。
单次遍历算法
滑动回收,实时计算出对象的转发地址而不需要额外的开销。此算法时对Lisp2的缺陷上的一个补充,它可以提前记录对象需要移动的位置(偏移向量,标记向量以及内存索引号)。
关于整理(压缩)算法的总结
所有现代的标记-整理回收器均使用滑动整理算法,它不会改变对象的相对顺序,也就不会影响赋值器的空间局部性。复制式回收器甚至可以通过改变对象布局的方式,将对象与其父节点或者兄弟节点排列的更近,以此提高赋值器空间局部性。
整理(压缩)算法的限制
整理算法的限制,如任意顺序算法只能处理单一大小的对象,或者针对大小不同的对象需要分批处理;整个过程需要2次或者3次遍历堆空间;对象头部可能需要一个额外的槽来保存迁移的信息。
分代收集理论
当前主流商业JVM的垃圾收集器,大多数都遵循了分代收集(Generational Collection)的理论进行设计,分代收集并不是垃圾回收算法,只是一种理论,一套指导方针,一套符合大多数程序运行实际情况的经验法则,它建立在几个分代假说之上。
分代收集三大假说
弱分代假说:绝大多数对象朝生夕死
强分代假说:活的越久的对象,也就熬过很多次垃圾回收的对象也是越来越难以消亡的。
跨代引用假说:老年代指向新生代。
老年代指向年轻代会会导致该回收的垃圾没有被回收,这里可以使用记忆集,记忆集会吧老年代划分成很多小块区域,来记录老年代内存跨代引用。GCRoot扫描时只有包含跨代引用的内存加入扫描。