引用计数
在C/C++语言中,程序员负责动态分配和回收堆heap当中的内存。在C里,通过函数 malloc() 和 free() 来完成。在C++里是操作 new 和 delete 来实现相同的功能。
每个由 malloc() 分配的内存块,最终都要由 free() 退回到可用内存池里面去。而调用 free() 的时机非常重要,如果一个内存块忘了 free() 则会导致内存泄漏,这块内存在程序结束前将无法重新使用。这叫做 内存泄漏 。而如果对同一内存块 free() 了以后,另外一个指针再次访问,则再次使用 malloc() 复用这块内存会导致冲突。这叫做 野指针 。等同于使用未初始化的数据,core dump,错误结果,神秘的崩溃等。
内存泄露往往发生在一些并不常见的代码流程上面。比如一个函数申请了内存以后,做了些计算,然后释放内存块。现在一些对函数的修改可能增加对计算的测试并检测错误条件,然后过早的从函数返回了。这很容易忘记在退出前释放内存,特别是后期修改的代码。这种内存泄漏,一旦引入,通常很长时间都难以检测到,错误退出被调用的频度较低,而现代电脑又有非常巨大的虚拟内存,所以泄漏仅在长期运行或频繁调用泄漏函数时才会变得明显。因此,有必要避免内存泄漏,通过代码规范会策略来最小化此类错误。
Python通过 malloc() 和 free() 包含大量的内存分配和释放,同样需要避免内存泄漏和野指针。他选择的方法就是 引用计数 。其原理比较简单:每个对象都包含一个计数器,计数器的增减与对象引用的增减直接相关,当引用计数为0时,表示对象已经没有存在的意义了,对象就可以删除了。
另一个叫法是 自动垃圾回收 。(有时引用计数也被看作是垃圾回收策略,于是这里的"自动"用以区分两者)。自动垃圾回收的优点是用户不需要明确的调用 free() 。(另一个优点是改善速度或内存使用,然而这并不难)。缺点是对C,没有可移植的自动垃圾回收器,而引用计数则可以可移植的实现(只要 malloc() 和 free() 函数是可用的,这也是C标准担保的)。也许以后有一天会出现可移植的自动垃圾回收器,但在此前我们必须与引用计数一起工作。
Python使用传统的引用计数实现,也提供了循环监测器,用以检测引用循环。这使得应用无需担心直接或间接的创建了循环引用,这是引用计数垃圾收集的一个弱点。引用循环是对象(可能直接)的引用了本身,所以循环中的每个对象的引用计数都不是0。典型的引用计数实现无法回收处于引用循环中的对象,或者被循环所引用的对象,哪怕没有循环以外的引用了。
循环检测器能够检测垃圾回收循环并能回收它们。 gc 模块提供了一种运行该检测器的方式 (collect() 函数),以及多个配置接口和在运行时禁用该检测器的功能。 |