GC 要做两件事
1. 找到内存空间里的垃圾
2. 回收垃圾,让程序员能再次利用这部分空间
主要有三大类算法其他都是扩展
- GC 标记- 清除算法
- 引用计数法
- GC 复制算法
GC 标记- 清除算法
简单描述:分为标记阶段和清楚阶段。标记阶段遍历堆(根的直接引用 然后递归)为所有的活动对象打上标记。清除阶段(从堆的开始到结束遍历每一个对象)如果是已经标记的,设置为未标记,未标记的连接到空闲链表(用作以后的分配),如果有连续的分块则合并。
如何分配:遍历空闲链表根据策略选择合适的对象(例如First - fit(遇到大于等于的分块就直接返回)、Best - fit(返回大于等于的最小分块)、Worst - fit(返回最大的分块)
优点:
- 简单
- 与保守式GC 算法兼容(也就是不移动对象)
缺点:
- 碎片化(也就是空闲链表中存在,一堆不连续的小分块,不论采用那种分配策略都无法避免碎片化)
- 分配速度慢(每次分配都需要遍历空闲链表,举个极端的,每次合适的都是最后一个)
- 与写时复制技术不兼容(写时复制技术(众多UNIX 操作系统的虚拟存储中用到的高速
化方法。),复制进程的时候大部分内存不会被复制而是共享,共享内存不能直接重写,想要重写得复制到自己的私有空间,不访问共享内存)
引用计数法
简单描述:每个对象有个计数器,在每次指针更新时,新指向的对象的计数器++,之前指向的对象的计数器--,如果等于0回收,加入空闲链表。
如何分配:遍历空闲链表
优点:
- 可即可回收垃圾(当计数器为0时直接回收)
- 最大暂停时间短(每次指针更新时都会回收垃圾)
- 没有必要沿指针查找(上面的标记清楚算法,需要由根开始查找)
缺点:
- 计数器值的增减处理繁重
- 计数器要占用很多位
- 实现繁琐复杂
- 循环引用无法回收(比如根引用A ,A和B两个互相引用,当根不引用A的时候 A和B已经是垃圾了,但是互相引用计数器都是1,无法回收)
GC 复制算法
简单描述:把堆平分为From,To两个空间,如果现在活动对象,垃圾是在From空间,从根开始递归(深度遍历),把所有的对象复制到To空间,反之亦然。
如何分配:当前活跃的空间,未分配的部分,按内存顺序分配。
优点:
- 优秀的吞吐量(能较短时间完成GC,和标记清楚算法比较,标记清楚需要遍历两次,而GC复制遍历一次+复制)
- 可实现高速分配(直接在未分配的连续空间分配,不需要遍历啥的)
- 不会发生碎片化(每次执行GC都把活动对象集中在开头,放在堆的一端的行为叫做压缩)
- 与缓存兼容(深度遍历的复制,有引用关系的对象,在内存中放到了一起)
缺点:
- 堆使用效率低下(一分为二,只有一半的堆能使用)
- 不兼容保守式GC 算法
- 递归调用函数(每次进行复制的时候都要调用函数,递归会消耗栈,所以还可能栈溢出)
补充 :根引用时来自静态变量,cpu寄存器以及局部变量或参数实例的任何引用。