1、标记-清除
将堆中对象全部进行扫描一遍,分别进行标记:存活对象(Live Object)、未被引用的对象(UnReference Object)、未使用空间(Un Use)
如何判定存活对象? 引用算法、引用链算法(可达性分析)
引用算法 :对象被引用,则计数+1,通过计算来判断是否被引用,但是回出现循环引用问题,导致对象无法被回收
引用链算法 :解决了循环引用问题,对象的引用,必须从GC Root(方法区、虚拟机栈、本地方方法栈)出发,这样判断对象是否存活
清除 :直接清除掉被标记需要回收的对象,释放出对应的内存空间
缺点 : (1)标记和清除两个过程都比较耗时,效率不高
(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2、标记-复制
场景 : 发生在young区里面的survivor区里的 from、to,两个内存大小一样,通过标记之后,将from里存活对象复制到to里,然后将from里的内存空间一次性清理出来
优点 :连续存放,这样就不会存在磁盘碎片
缺点 :典型的空间换时间,降低了内存空间的利用率
3、标记-整理
场景 :发生在old区,标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点 :提高了内存空间的利用率
缺点 :回收耗时比其他都长,效率不高
整理的算法 :
双指针算法 :第一步 :两个指针,一个在头,一个在尾,头扫描空闲空间停下来,尾扫描引用对象,则将引用对象移动到头所在的空闲空间,直到两个指针碰撞
第二步 :修改引用地址
适用场景 :固定大小的对象,
Lisp2(滑动算法) :第一步 :三个指针,一个找可达对象,一个找内存空间,最后一个固定在最后面,形成闭环,扫描做记录,有个记录内存的地方
第二步 :修改引用地址
第三步 :移动对象
缺点 :效率慢,循环三次
单次遍历算法 :额外的表,去记录数据 (标记位向量、偏移位向量、内存索引号)
将内存分为很多模块,记录对象在内存模块中的位置(标记位向量),扫描全部对象时,记录对象所要偏移的位置(偏移位向量),记录对象在内存中的地址(内存索引号)
通过这三个值,便可以移动对象,修改引用地址
4、分代收集
Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)
Old区:标记清除或标记整理(Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理)