概述

本文只讨论算法思想,不会具体到某种语言的实现方式以及额外细节。

介绍如下内容:

  • 什么是引用计数
  • 优缺点分析

什么是引用计数

引用计数是一种用于找出垃圾对象的方法。

  1. 要使用一个对象则必须持有指向这个对象的引用。
  2. 某个对象的引用计数就是指向这个对象的引用的数量。
  3. 如果某个对象的引用计数不为零则不对其进行回收。
  4. 如果某个对象的引用计数为零则需要对其进行回收。
  5. 对于原始的引用计数策略,如果某个对象引用计数为零则会立即回收,不会延迟回收。

如下图所示

图片加载失败

引用计数在何时增加

某个对象的引用计数在某个引用指向该对象时加1,典型情况如下

  • 情况一:

    Object p = new Object();

    上述代码执行完毕后对象的引用计数为1。

  • 情况二

    Object p = new Object();
    Object q = p;

    上述代码执行完毕后对象的引用计数为2,因为p和q同时持有指向这个对象的引用。

  • 情况三:

    void Foo(Object p) {
      // TODO
    }
    

    当这个函数开始执行的时候,如果p != null则对应的对象的引用计数加1,因为当对象作为参数传递的时候是指就是拷贝一份指向对象的引用作为参数。

  • 情况四:

    // Fun()返回一个对象
    Object p = Fun();

    如果函数返回值不为空,则上述代码执行完毕后对象的引用计数加1,因为当对象作为返回值传递的时候是指就是拷贝一份指向对象的引用作为返回值。

  • 情况五:如果某个对象中嵌套了其它对象,那么这些嵌套进去对象依然适用上述情况。

引用计数何时减少

当指向某个指向对象引用失效时则相应的对象的引用计数减1,典型情况如下

  • 情况一:

    void Foo() {
      Object p = new Object();
    }

    上述代码执行完毕后变量p会被自动销毁,所以相应对象的引用计数自动减1,由于这个对象是在Foo()且只在这里创建,所以当Foo()返回时这个对象的引用计数就是0,会被当做垃圾回收。

  • 情况二

    Object p = new Object();
    p = null;

    p = null;这行代码执行完后,由于p不再持有指向该对象的引用,所以该对象的引用计数减1。同时由于这个指向这个对象的引用只被p所持有,所以这个对象引用计数会变为0,之后会被当做垃圾回收。

  • 情况三

    void Foo(Object p) {
      // TODO
    }
    

    当这个函数结束的时候,如果p != null则对应的对象的引用计数减1。因为当对象作为参数传递的时候是指就是拷贝一份指向对象的引用作为参数。

  • 情况四:如果某个对象中嵌套了其它对象,那么这些嵌套进去对象依然适用上述情况。

优缺点分析

优点

  • 实现相对简单
  • 引用计数策略允许在检测到垃圾对象后立即回收,比较适合内存资源紧张的环境,并且避免一些因为回收不及时导致的错误。

缺点

  • 由于引用计数的改变无法推迟,所以GC系统就无法选择一个合适的时机统一检测,会导致运行效率下降。例如GC系统可以选择程序在等待网络IO时检测,因为等待网络IO的过程中是无法执行接下来的代码的,在此时进行检测可以利用空闲的时间。

  • 无法解决循环引用问题,如下:

    class A {
      B ref;
    }
    
    class B {
      A ref;
    }
    void Foo() {
      A p1 = new A();
      B p2 = new B();
      p1.ref = p2;
      p2.ref = p1;
    }

    上述函数返回后对象的引用情况如下图

    图片加载失败

    此时这两个对象的外部没有任何一个对象持有指向两者的引用,也就无法操作这两个对象。可以比照上文中提到的“何时减少引用计数”中的情况,可知在这种情况下这两个对象是无法被回收的。

    不过如果我们对代码稍作改变就可以通过手动的方式让这些对象得到回收

    void Foo() {
      A p1 = new A();
      B p2 = new B();
      p1.ref = p2;
      p2.ref = p1;
      p1.ref = null;
      p2.ref = null;
    }

    上述函数返回后对象的引用情况如下图

    图片加载失败

    这时对象就可以正常回收了,不过这种需要程序员介入的方式显然不是垃圾回收的初衷