概述
引用计数法又是什么鬼呢? 顾名思义, 对对象的引用进行计数. 通过记录每个对象被引用的次数, 来确定这个对象是否可以被回收.
实现
首先, 对对象的引用数量进行管理, 什么时候会更新呢?
- 创建对象: 新建一个对象(对这个新的对象引用数量+1)
- 更新指针: 将一个指向A对象的指针重新指向B对象(将A对象引用数量-1, B对象引用数量+1)
这次就不上代码了, 简单介绍一下思路就行. (我哥说代码看着费劲)
前提: 我们有一个全局的空闲地址链表: FREE_HEAD
创建对象的操作
- 从FREE_HEAD中寻找内存
- 若找到了, 该对象计数器置为1, 返回
- 若没有找到, 内存扩容, 返回1
更新指针的操作
- 将新的对象引用计数+1
- 将旧的对象引用计数-1. 若-1后引用数量为0, 则将该对象及所有的子对象添加到
FREE_HEAD
链表中.
实现说起来简简单单, 毕竟我也不用真的去实现, 简单想一下.
分析
在上一次的标记清除算法
中, GC在每次内存不足时运行, 势必会导致程序暂停时间比较长. 但引用计数
则在每次指针变更的同时进行管理, 在产生新的垃圾的时候立刻进行回收. 这就体现出它的几个优势了:
- 最大暂停时间短.
- 产生垃圾可立即回收
当然, 只说优势不说劣势都是扯犊子. 首先, 引用计数
的优势也会成为它的劣势, 计数频繁的计算, 会拖累程序的速度. 而且每个对象都要开拓空间来保存引用数量. 当然了, 还有经常被说到的循环引用的问题. 等等吧.
- 频繁的更新引用计数拖累程序速度
- 每个对象需要开拓额外空间保存引用计数
- 循环引用对象无法被回收(就是A引用B, B引用A. 但是他们都没有被其他对象引用, 导致他俩的引用始终为1, 无法回收)
当然, 针对问题, 伟大的前人总是有办法去解决. 比如:
延迟计数法: 针对频繁更新计数器的问题而提出的. 大概意思就是不去实时的对引用数量进行更新, 将引用数量为0的记录到一个待处理的链表中, 当需要新的内存时再统一处理. 但是这样又会增大暂停时间, 才不要.
Sticky引用计数法: 引用计数通过额外的空间保存引用数量, 但这个必然会有最大值, 比如用1个字节, 则引用数量超过256的就记不下了. 这个方法对超出范围的处理方式很简单, 什么都不做, 不去回收, 毕竟被引用这么多次, 该对象定然很重要. 那这些对象不就永远都不能被回收了么? 可以, 等到没有内存了, 使用标记清除算法
将所有对象过一遍.
当然, 针对引用计数法还有很多演变, 有些还是很有意思的, 有些是我看不懂的.
垃圾回收的整体思路分两个流派(我所知道的):
- 引用计数: 就是上面说的这种
- 可达性: 就是
标记清除
那种, 判断一个对象是否可以到达.
引用计数
的最大优势应该就是不需要暂停程序去进行回收了, 随使用随回收. 但劣势也很明显: 需要计数器额外空间以及循环引用的问题.
个人是比较喜欢引用计数
的, 实时性又高, 又不需要太多的额外空间. 只是需要在编写代码的时候刻意规避循环引用, 或者其他方法规避一下? 甚至不去处理都刻意, 如果只有少数的话(如果有很多, 还是换个算法吧).