Python技术迷

python面试题:Python垃圾回收机制

你写 Python 写久了,总会在某个时刻开始疑惑:我这对象到底什么时候被回收的?内存是不是慢慢涨起来了?其实这些问题的背后,就是 Python 的垃圾回收(GC)机制。

Python 的垃圾回收机制,严格来说分两套:一套是引用计数(Reference Counting),这是基础;另一套是垃圾回收器,主要解决循环引用的问题。

先说引用计数,这是 Python 最核心的内存管理方式。每当你创建一个对象、赋值、传参等等,Python 都会在内部维护一个“引用计数器”:

import sys

a = []
print(sys.getrefcount(a))  # 通常是2,因为getrefcount也临时引用了它

只要这个对象的引用计数变成 0,Python 会立刻释放这个对象的内存。这个机制很高效,几乎是“实时释放”,不像 Java 那种要等 GC 扫描一轮。

问题来了,如果两个对象互相引用,比如:

classNode:
def__init__(self):
        self.other = None

a = Node()
b = Node()
a.other = b
b.other = a

这种叫循环引用,两个对象的引用计数永远不是 0,就算你 del a 和 del b,它们还会互相引用,内存不会被释放。这就是引用计数的致命弱点。

所以 Python 还引入了一个分代垃圾回收器(Generational GC),它是基于对象“生命周期”的观察。大部分对象都是“短命的”,比如函数里的临时变量。而存活时间越长的对象,越不容易被清理。

GC 把对象分为三代:

  • 第0代:新创建的对象
  • 第1代:从第0代幸存下来的
  • 第2代:活得最久的对象

GC 会频繁扫描第0代,较少扫描第1代,很少扫描第2代。为什么?因为“懒得管你老东西,基本不会死了”。这策略跟 Java 那一套挺像,但实现细节不一样。

你可以通过 gc 模块查看和调试垃圾回收:

import gc

print(gc.get_threshold())  # 各代触发阈值
print(gc.get_count())      # 当前各代对象的数量

还可以手动控制:

gc.collect()  # 强制执行一次GC

甚至你可以禁用自动回收:

gc.disable()

当然,一般情况别这么干,容易把自己玩死。除非你真的要精细控制内存,比如在某些高性能后台服务中。

还有个隐藏点是:某些模块或者库里的 C 扩展对象,不一定参与 Python 的 GC,尤其是没有实现 tp_traverse 和 tp_clear 的 C 对象,可能就被丢在那儿了。这在你写 Cython 或嵌入 C 模块时要特别注意。

实战里还有一种情况是:你注册了回调或绑定事件,导致对象“被持久引用”了,但你没意识到。这也会导致 GC 无法回收。比如:

classA:
def__del__(self):
        print("对象被回收")

a = A()
b = a
del a
del b  # 只有在引用计数为0时,__del__ 才被调用

你可以重写 __del__ 方法来观察对象回收情况,但这不是推荐的做法,它有很多限制,比如循环引用中的对象根本不会被自动 __del__,还可能引发资源泄漏。

所以结论是:Python 的内存回收虽然自动,但不是“完全不用管”。你要知道引用计数+GC这套机制的行为,才能写出健壮的程序,特别是在数据流、缓存管理、长生命周期服务里。

面试最优回答:

Python 的垃圾回收机制以引用计数为核心,同时结合了分代垃圾回收器来处理循环引用。每个对象都有一个引用计数器,当引用数为0时对象会立即被销毁。但对于像互相引用的对象,可能会导致循环引用问题。

为了解决这个问题,Python 引入了分代回收机制,将对象分成三代,并根据其生命周期决定回收频率。在项目中遇到过事件系统绑定对象导致内存泄露的情况,最终通过使用弱引用(weakref)配合 GC 模块调试解决。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

也可以看我写的这篇文章《DeepSeek满血复活,直接起飞!》来进行本地搭建。

对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
🔥虎哥私藏精品 热门推荐🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》。

资料包含了《IDEA视频教程》、《最全python面试题库》、《最全项目实战源码及视频》及《毕业设计系统源码》,总量高达650GB,全部免费领取。