引用计数
复习
- 语言运行时:负责内存管理等服务
- 对象布局:对象在内存中的表示
- 时间与空间的交换:用空间换时间
TL;DR
- 引用计数为每个对象记录被引用的次数
- 计数归零就回收,及时而且简单
- 代价是每次引用变化都要维护计数
- 最大的问题是无法回收“循环引用”
正文
自动内存管理有两条主要路线,先看比较直观的一条:引用计数(reference counting)。
数一数有多少人在用它
引用计数的想法很朴素:给每个对象记一个数,表示“当前有多少地方在引用它”。
- 有人引用它(比如赋给一个变量、放进一个容器),计数加一
- 有人不再引用它,计数减一
- 计数减到 0,说明再也没人用它了,立刻回收
它的优点是及时:没人用了马上回收,不用等;实现也相对简单。程序一旦不再引用某对象,内存立刻就能腾出来。
代价
但“每次引用变化都要改计数”,本身就是开销:
- 每次赋值、传参、出作用域……都可能触发计数增减
- 频繁的计数操作会拖慢程序
- 多线程下,计数还得同步,开销更大
- 每个对象还要多存一个计数字段,占空间
所以,引用计数是“用运行时的计数开销,换及时、简单的回收”。又是一次交换。
循环引用:它的死穴
引用计数有一个著名的问题:循环引用。
设想两个对象互相引用:A 引用 B,B 也引用 A,此外再没人用它们了。此时:
- A 的计数是 1(被 B 引用),不是 0
- B 的计数也是 1(被 A 引用),不是 0
于是它们谁也不会被回收,即使整个程序已经用不到它们了。这对引用计数来说,是死结——因为它是靠“局部的计数”判断的,看不到“整体都已无用”这个事实。
要解决循环引用,就需要一种全局视角的回收方式:从“谁还在用”出发,判断哪些对象真正还能被到达。这就是下一章的追踪式垃圾回收。
思考题 1
引用计数在什么时候回收对象?
思考题 2
引用计数的“循环引用”问题,是什么?
小结
知识点
- 引用计数为每个对象记录被引用次数
- 计数减到 0 时立即回收
- 优点是及时、简单,代价是计数开销
- 无法回收互相引用的对象
参考资料
- Wikipedia(zh):引用计数:按引用数量管理内存
- Wikipedia(zh):垃圾回收:自动内存管理的机制
思考题答案(仅供参考)
思考题 1
当某个对象的引用计数减到 0 时,说明已经没有任何地方引用它,引用计数机制便会立即回收它。回收是即时的,不必等待其他时机。
思考题 2
当两个或多个对象互相引用、形成环,而外部已不再引用它们时,它们彼此的计数都不为 0,于是都不会被回收。即使实际已无人使用,内存也无法释放,这就是循环引用问题。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪