前面大致介绍了 Python 的内存管理模型,其采用了分层策略管理内存,另外通过 Object 对象头部中的 ref_cnt 记录对象的引用数量。回顾 IntObject、StringObject 等,一个对象被创建的时候,Object_New 接口会直接将对象的引用计数设置为 1,即当前变量引用。之后每次变量 “赋值” 都会使得对象引用自增 1,变量被注销则对应的引用计数自减,减至 0 时就立刻触发真正的 free 接口,对于小对象,会将内存释放回内存池,否则释放回操作系统。这是一种即时性很好的内存管理策略,不需要暂停整个系统进行垃圾回收。
凡事都有不完美的地方,引用计数策略最大的问题在于无法处理 “循环引用”,比如两个 list 互相引用:
l1 = []
l2 = []
l1.append(l2)
l2.append(l1)
结果 l1 和 l2 实际上都只被彼此需要,它们的引用计数都不为零,但是实际上已经没有变量引用它们了。这些构成循环引用的对象可以更多、更复杂,在内存中,它们与真正使用中的对象不同,像一个气泡一样独立存在。
这便是引用计数的局限性,因此 Python 引入了 GC 解决这个问题。
也许你注意到,在 Python 中只有那些容器才有可能出现循环引用问题,像 IntObject、StringObject 之类的简单对象只需要引用计数就可以了。事实上,如 ListObject、DictObject 都是通过 GC_New 创建的,以便 GC 可以跟踪它们。
Python 的 GC 采用 标记-清除策略,即通过遍历上图中所有对象的引用路径标记,未被标记的对象就是可以被清除的对象。下一节我们继续研究。