引用计数法

基本原理:在每个对象中保存该对象的引用计数,当引用发生增减时对计数进行更新。引用计数的增减,一般发生在变量赋值、对象内容更新、函数结束(局部变量不再被引用)等时间点。当一个对象的引用计数变为0时,则说明它将来不会再被引用,因此可以释放相应的内存空间。

img

// 创建对象时,将其引用计数赋值为1
new_obj(size) {
    obj = pickup_chunk(size, $free_list)
    if (obj == NULL) {
        allocation_fail()
    } else {
        obj.ref_cnt = 1
        return obj
    }
}

// 将对象赋值给一个指针时,将指针原来引用的对象的引用计数减一
// 必须引用的先做加操作,若先做减操作,可能导致引动对象的计数器变成0,直接被回收掉
update_ptr(ptr, obj) {
    // 对指针ptr新引用的对象的引用计数器加一(小P: 新墙头~我来了~
    inc_ref_cnt(obj)
    // 对指针ptr之前指向的对象的引用计数器减一(小P: 对不起,你失去本指针了
    dec_ref_cnt(*ptr)
    *ptr = obj
}

计数器增减操作:

/ 引用计数器加一操作
inc_ref_cnt(obj) {
    obj.ref_cnt++
}

// 引用计数器减一操作
dec_ref_cnt(obj) {
    obj.ref_cnt--
    // 如果obj的引用数为0,那么它就要被回收了,所有被它引用的对象的引用计数器也要递减
    if (obj.ref_cnt == 0) {
        for (child: obj.children) {
            dec_ref_cnt(child)
        }
        // 回收obj,将obj连接到空闲链表
        reclaim(obj)
    }
}

优点:

  • 可以即刻回收
  • 暂停时间短-由于垃圾回收在每次指针变更时都会进行,所以单次垃圾回收量并不是很大,相当于分散回收,时间变短。
  • 在需要减少沿指针查找的次数时,引用计数法具有显著优势。
  • 可以在代码中实现,无需作为语言的运行时环境的一部分,开发者可以完全控制引用计数的使用

缺点

  • 计数器得值更新太过频繁;
  • 计数器可能占用很大空间;
  • 无法回收循环引用的对象;

延迟引用计数法

针对计数器频繁更新进行优化

  • 局部变量引用不计入引用计数,默认其会随着栈消亡;
  • 用哈希表保存对象的引用书;

延迟引用计数法最主要的优点就是减轻了引用频繁发生变化导致的计数器增减带来的负担。其缺点也很显然:垃圾不能马上得到回收,失去了可即刻回收垃圾这一大优点依然没有解决循环引用的问题,要与跟踪垃圾收集器 (tracing garbage collector) 配合使用。

类似方法:

  • 合并:跟踪对象在某一时段开始和结束的状态,忽略中间过程引起的计数值变量。
  • 缓存:将所有引用计数值增减操作缓冲起来以便后续进行处理,同时只有回收线程可以执行引用计数变更操作。缓冲引用计数关注的是何时执行变更,而不是是否执行变更。(限制操作计数器的线程)