引用计数是 Python 内存管理的第一道防线,能快速回收大多数不再使用的对象。但它有一个致命弱点:无法处理循环引用。也就是说,如果两个或多个对象互相引用,即使外部不再使用它们,引用计数永远不会归零,导致内存泄漏。
标记-清除(Mark-Sweep)算法专门为了解决这个问题而生。它的工作方式是分两步走:
- 标记阶段
Python 从一组“根对象”出发开始扫描。根对象包括全局变量、调用栈中的局部变量、寄存器中引用的对象等——这些是程序明确还在使用的对象。
从根对象出发,沿着引用链递归遍历所有能到达的对象,并给它们打上“存活”标记。
遍历结束后,没有被打上标记的对象就是潜在的垃圾——它们可能形成了循环引用,从根对象出发根本访问不到它们,但彼此之间引用计数不为零。
- 清除阶段
将这些无标记的对象全部销毁,释放内存。对于那些定义了 del() 方法的对象,Python 不会自动清理它们,而是把它们收集到 gc.garbage 列表中,由开发者手动处理,以免触发不可预测的析构逻辑。
实际触发机制
Python 内部维护了一个双向链表,把所有可能产生循环引用的容器对象(如 list、dict、set、自定义类实例等)都串在一起。当分配的对象数量减去释放的对象数量超过阈值(gc.get_threshold(),默认是 700)时,就会触发一次垃圾回收。标记-清除就是在这时启动的。
和引用计数的配合
日常开发中,我们不用操心这些细节。但理解它的存在有助于写出更省内存的代码:避免不必要的循环引用,及时用 del 断开大型数据结构之间的引用,能在某些场景下让内存释放更快。同时,这也是为什么写 del() 需要格外小心——一旦对象形成循环引用且定义了 del(),GC 就无法自动清理它,会成为内存隐患。