问题: Python 垃圾回收机制

👇 点击展开/折叠 面试背诵模板(建议先背这个) Python 垃圾回收采用引用计数为主、分代回收为辅的策略。引用计数记录对象被引用的次数,为0时立即销毁(CPython,这是最基础也是最重要的机制)。分代回收解决循环引用问题,将对象按存活时间分为0/1/2三代,新对象在0代,存活时间越长代别越高,回收频率越低。可通过 gc 模块手动干预:gc.collect() 强制回收,gc.disable() 关闭自动回收,gc.set_threshold() 调整阈值。循环引用中重写了 __del__ 的对象无法被回收,会进入 gc.garbage 列表造成内存泄漏。
---

引用计数(核心机制)

  • 原理:每个对象维护一个引用计数字段,新增引用时+1,引用失效时-1,计数归零时立即调用析构函数释放内存。
  • 优点:实时性高,内存回收及时,无停顿(STW)问题。
  • 缺点:无法处理循环引用(如两个对象互相引用),需配合分代回收;每次引用变化都要更新计数器,有额外性能开销。

分代回收(辅助机制)

  • 原因:解决循环引用导致的内存泄漏。
  • 实现:将对象按“存活代数”分组:
    • 0代(年轻代):新对象,回收最频繁。
    • 1代(中年代):经历0代回收后存活的对象,回收频率较低。
    • 2代(老年代):经历1代回收后存活的对象,回收频率最低。
  • 触发时机:当某代对象数量超过阈值(可通过 gc.get_threshold() 查看)时触发回收。
  • 算法:使用“标记-清除”(Mark-Sweep)先标记可达对象,再清除不可达对象。

手动干预与调试

操作 代码 说明
强制回收 gc.collect() 立即触发分代回收(可指定代数)
查看阈值 gc.get_threshold() 返回 (0代阈值, 1代阈值, 2代阈值)
设置阈值 gc.set_threshold(700, 10, 5) 调整回收触发频率
禁用GC gc.disable() 关闭自动回收,提升性能(慎用)
启用GC gc.enable() 恢复自动回收
查看无法回收对象 gc.garbage 包含因 __del__ 导致无法回收的对象列表

常见陷阱与排查

  • **循环引用 + __del__**:若循环引用中的对象定义了 __del__,分代回收无法确定销毁顺序,对象会进入 gc.garbage 列表且永不释放,造成内存泄漏。解决方案:尽量避免在循环引用类中定义 __del__,或使用 weakref 弱引用。
  • 内存泄漏排查:使用 objgraph 或 tracemalloc 模块定位未回收对象,或通过 gc.get_objects() 查看所有存活对象(谨慎用于生产环境)。

Python GC vs Java/Go GC(面试加分)

特性 Python(CPython) Java(HotSpot) Go
策略 引用计数 + 分代回收 分代回收(复制/标记整理) 非分代并发三色标记
停顿 引用计数无停顿,分代回收有短暂停顿 有停顿(但优化后可达毫秒级) 低停顿(并发执行)
循环引用处理 需分代回收辅助 根可达性分析自动处理 根可达性分析自动处理
实时性 高(引用计数立即回收) 低(延迟回收) 中(并发回收)