引用计数算法

作者: 可文分身 | 来源:发表于2014-02-23 20:38 被阅读1704次

前言

相比于前面三种垃圾收集算法,引用计数算法算是实现最简单的了,它只需要一个简单的递归即可实现。现代编程语言比如Lisp,Python,Ruby等的垃圾收集算法采用的就是引用计数算法。现在就让我们来看下引用计数算法(reference counting)是如何工作的。

算法原理

引用计数算法很简单,它实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收。

比如说,当我们编写以下代码时,

String p = new String("abc")

abc这个字符串对象的引用计数值为1.



而当我们去除abc字符串对象的引用时,则abc字符串对象的引用计数减1

p = null

由此可见,当对象的引用计数为0时,垃圾回收就发生了。这跟前面三种垃圾收集算法不同,前面三种垃圾收集都是在为新对象分配内存空间时由于内存空间不足而触发的,而且垃圾收集是针对整个堆中的所有对象进行的。而引用计数垃圾收集机制不一样,它只是在引用计数变化为0时即刻发生,而且只针对某一个对象以及它所依赖的其它对象。所以,我们一般也称呼引用计数垃圾收集为直接的垃圾收集机制,而前面三种都属于间接的垃圾收集机制。

而采用引用计数的垃圾收集机制跟前面三种垃圾收集机制最大的不同在于,垃圾收集的开销被分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。这个也可以从它的伪代码实现中看出:

New(): //分配内存
    ref <- allocate()
    if ref == null
        error "Out of memory"
    rc(ref) <- 0  //将ref的引用计数(reference counting)设置为0
    return ref

atomic Write(dest, ref) //更新对象的引用
    addReference(ref)
    deleteReference(dest)
    dest <- ref

addReference(ref):
    if ref != null
        rc(ref) <- rc(ref)+1
        
deleteReference(ref):
    if ref != null
        rc(ref) <- rc(ref) -1
        if rc(ref) == 0 //如果当前ref的引用计数为0,则表明其将要被回收
            for each fld in Pointers(ref)
                deleteReference(*fld)
            free(ref) //释放ref指向的内存空间

对于上面的伪代码,重点在于理解两点,第一个是当对象的引用发生变化时,比如说将对象重新赋值给新的变量等,对象的引用计数如何变化。假设我们有两个变量p和q,它们分别指向不同的对象,当我们将他们指向同一个对象时,下面的图展示了p和q变量指向的两个对象的引用计数的变化。

String p = new String("abc")
String q = new String("def")
p = q

当我们执行代码p=q时,实际上相当于调用了伪代码中的Write(p,q), 即对p原先指向的对象要进行deleteReference()操作 - 引用计数减一,因为p变量不再指向该对象了,而对q原先指向的对象要进行addReference()操作 - 引用计数加一。

第二点需要理解的是,当某个对象的引用计数减为0时,collector需要递归遍历它所指向的所有域,将它所有域所指向的对象的引用计数都减一,然后才能回收当前对象。在递归过程中,引用计数为0的对象也都将被回收,比如说下图中的phone和address指向的对象。

环形数据问题

但是这种引用计数算法有一个比较大的问题,那就是它不能处理环形数据 - 即如果有两个对象相互引用,那么这两个对象就不能被回收,因为它们的引用计数始终为1。这也就是我们常说的“内存泄漏”问题。比如下图展示的将p变量赋值为null值后所出现的内存泄漏。

后记

到今天为止,四种基本的垃圾收集算法就都介绍完了。每种算法都有它自己的优点和缺点。同时每种基本算法还有它自己的优化算法,但是在这里我只专注于介绍基本的原理,让大家知道它们是怎么工作的,对于它们的优化算法,大家可以自己查阅资料进行学习。后面我们会来看下这几种基本垃圾收集算法怎么组合成更加高级的垃圾收集算法,比如说分代垃圾收集算法等。

相关文章

  • 垃圾回收-GC

    1. 垃圾回收算法 垃圾回收算法包括引用计数算法、标记整理、标记复制、标记清除。 1). 引用计数算法 引用计数算...

  • JAVA判断一个对象生存还是死亡

    JAVA中判断一个对象是否死亡的算法有两种: 引用计数算法 可达性分析算法 一、引用计数算法 所谓引用计数算法就是...

  • Android 性能优化-GC确定回收的算法

    本文中分享两种GC确定回收的算法 引用计数算法以及可达性分析算法 引用计数算法:简单来说引用计数算法就是当前内存地...

  • 关于GC(二)关于回收算法

    引用计数算法 可达性算法2.1 标记清除算法2.2 复制算法2.3 标记整理算法 一、 引用计数器算法 算法实现简...

  • JVM之垃圾回收

    1、引用计数算法 引用计数算法的原理很简单:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值+1;当引用...

  • 对象已死与垃圾回收算法

    1. 对象已死 1.1 引用计数算法 引用计数算法是指添加一个引用计数器,每被引用一次,计数器就加一,当引用失效时...

  • 《深入理解Java虚拟机》学习笔记(三)垃圾回收

    判断对象是否存活引用计数算法:为对象添加引用计数器,引用计数器+1,引用失效计数器-1,算法简单,效率高,很难解决...

  • JVM - GC算法

    JVM - GC算法 对象存活判断算法 引用计数法:每个对象内置一个引用计数器,新增引用时,计数器加1,释放引用时...

  • java垃圾回收算法

    垃圾回收算法: ①引用计数算法:每一块对象内存都保存了一个计数器,用来记录当前对象被引用的次数,引用计数算法就是当...

  • 第三章 垃圾收集器与内存分配策略

    对象存活判定算法 引用计数算法 原理是每个对象都有一个引用计数器,当被引用时,计数器加1,取消引用时计数器减1。计...

网友评论

  • 秋纫:貌似C++11的智能指针也引入了这种机制。

本文标题:引用计数算法

本文链接:https://www.haomeiwen.com/subject/cnmhtttx.html