gc种类分为
1.标记-清除算法
通过标记阶段将所有活动对象做上标记,在清理阶段清除没有标记的死亡对象。
gc() {
mark_phase() {
for(r : roots)
mark(r)
};
sweep_phase();
}
roots 为用户变量、调用栈、寄存器。
优点:
实现简单
gc时不会移动对象
缺点:
内存碎片化、
分配速度慢(与其他的GC算法<复制、标记-压缩>可分配内存是连续的不同,该算法空闲内存为链表,极端情况可能遍历整个链表)、
与写时复制不兼容(同上一样,内存空间不连续,gc时修改标志位会导致大部分对象需要被复制)
改进:
多个空闲内存链表(链表按内存块大小区分,节省遍历时间)
分配大小相近的固定内存分块(比如申请800字节,只给1024字节的空间)
位图标记(将对象标记与对象分开,这样写时复制时只复制了位图的内存)
延迟清除(分配内存时执行一定清理工作以节省在stw时整体时间)
2.引用计数算法
通过在分配的内存对象前增加几位用于保存被引用次数。在计数为0时释放内存。使用该算法的程序无需主动调用gc()函数
优点:
立刻回收垃圾(在update操作中计数为0就立即被加入空闲链表)
最大暂停时间短(stw时间短)
不需要遍历
缺点:
计数器操作频繁压力大
计数器需要占用很大空间(理论情况下,一个32位程序可能有2^32个指针,都可能指向一个对象,那计数器为保持不出现异常至少需要32位空间,而当对象是char或short时就有些本末倒置了,效率低下)
实现复杂
循环引用无法回收
改进:
延迟引用计数(但是失去了引用计数最大优点:立即回收)
sticky引用计数法(计数器位空间很小,比如只给5位,如果有对象被引用次数超过可表达上限,可以有下面2种方法处理)
1.啥都不做。事实很多研究表明,大部分对象一出生就死亡,计数器就在0和1之间变化。
2.在适当时机使用标记-清理算法作为补充。
1位标记法(sticky的极端情况,优点是高速缓存命中优化,缺点极端不通用)
部分标记-清除算法(四种颜色巴拉巴拉不懂,优点解决循环引用,缺点丧失了引用计数stw时间短的最大优点)
3.gc复制算法
利用2个相同大小的内存空间进行分配清除。在from空间分配,当空间已满则将内存复制整理转移到to空间。如此循环。
对象头部会有是否被复制的tag。
优点:
优秀的吞吐量(和标记-清除算法(搜索活动对象,清理死亡对象)相反,复制算法只复制活动对象,)
高速分配(空间内存为连续空间,而非链表)
不会产生碎片(空间内存非链表)
高速缓存命中优化
缺点:
内存/堆空间使用率低下(只是用一半)
改进:
多空间复制(将空间分为多份,其中2份执行复制算法,其他执行标记-清除算法)解决了空间利用率低的问题,但同样引入标记-清除的缺点
4.gc标记-压缩算法
标记和1标记-清除的标记一样,压缩阶段产生的效果和3复制算法一样,但是不用牺牲空间。
优点:
有效利用空间
缺点:
压缩阶段耗时
5.分代式gc算法
分代式gc算法把对象根据活动时间长短(年龄)分成几代,使用不同的gc算法。新申请的对象为新生代,到达一定年龄的称为老年代
分代式gc算法不是独立的算法,而是将标记-清除、复制算法合理一并使用的算法。
如,新生代对象一般很快就死去,所以进行选择查找活动对象的gc算法(标记-清除的标记阶段、复制算法),这样时间短。老年代对象很难称为垃圾,则可以减少gc频率。
优点:
吞吐量改善
缺点:
”很多对象年纪轻轻就会死“这个经验并不适用所有情况。
改进:
多代式gc算法(十八代)
6.增量式gc算法
通过逐渐推进gc来控制最大stw时间的方法。通过三种颜色(白灰黑,白:还未搜索的对象,灰:正在搜索的对象,黑:搜索完成的对象)标记,控制gc的节奏
优点:
缩短最大暂停时间(增量式垃圾回收不是一口气运行 GC,而是和 mutator 交替运行的)
缺点:
吞吐量降低
v8 GC算法
v8 gc是准确型gc。所有对象通过Object对象准确引用。通过HandleScope作为根进行管理,
v8 gc属于分代式gc,新生代使用复制算法,老年代使用标记-清除算法和标记压缩算法。