Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

引用计数

复习

  • 语言运行时:负责内存管理等服务
  • 对象布局:对象在内存中的表示
  • 时间与空间的交换:用空间换时间

TL;DR

  • 引用计数为每个对象记录被引用的次数
  • 计数归零就回收,及时而且简单
  • 代价是每次引用变化都要维护计数
  • 最大的问题是无法回收“循环引用”

正文

  自动内存管理有两条主要路线,先看比较直观的一条:引用计数(reference counting)。

数一数有多少人在用它

  引用计数的想法很朴素:给每个对象记一个数,表示“当前有多少地方在引用它”。

  • 有人引用它(比如赋给一个变量、放进一个容器),计数加一
  • 有人不再引用它,计数减一
  • 计数减到 0,说明再也没人用它了,立刻回收

  它的优点是及时:没人用了马上回收,不用等;实现也相对简单。程序一旦不再引用某对象,内存立刻就能腾出来。

代价

  但“每次引用变化都要改计数”,本身就是开销:

  • 每次赋值、传参、出作用域……都可能触发计数增减
  • 频繁的计数操作会拖慢程序
  • 多线程下,计数还得同步,开销更大
  • 每个对象还要多存一个计数字段,占空间

  所以,引用计数是“用运行时的计数开销,换及时、简单的回收”。又是一次交换。

循环引用:它的死穴

  引用计数有一个著名的问题:循环引用。

  设想两个对象互相引用:A 引用 B,B 也引用 A,此外再没人用它们了。此时:

  • A 的计数是 1(被 B 引用),不是 0
  • B 的计数也是 1(被 A 引用),不是 0

  于是它们谁也不会被回收,即使整个程序已经用不到它们了。这对引用计数来说,是死结——因为它是靠“局部的计数”判断的,看不到“整体都已无用”这个事实。

  要解决循环引用,就需要一种全局视角的回收方式:从“谁还在用”出发,判断哪些对象真正还能被到达。这就是下一章的追踪式垃圾回收

思考题 1

  引用计数在什么时候回收对象?

思考题 2

  引用计数的“循环引用”问题,是什么?

小结

知识点

  • 引用计数为每个对象记录被引用次数
  • 计数减到 0 时立即回收
  • 优点是及时、简单,代价是计数开销
  • 无法回收互相引用的对象

参考资料

  1. Wikipedia(zh):引用计数:按引用数量管理内存
  2. Wikipedia(zh):垃圾回收:自动内存管理的机制

思考题答案(仅供参考)

思考题 1

  当某个对象的引用计数减到 0 时,说明已经没有任何地方引用它,引用计数机制便会立即回收它。回收是即时的,不必等待其他时机。

思考题 2

  当两个或多个对象互相引用、形成环,而外部已不再引用它们时,它们彼此的计数都不为 0,于是都不会被回收。即使实际已无人使用,内存也无法释放,这就是循环引用问题。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪