Skip to content

内存管理与垃圾回收

引用计数、标记清除、分代回收、循环引用、weakref。

Updated View as Markdown
For humans

内存管理与垃圾回收

Python 的内存管理是“引用计数为主、分代回收为辅”的混合机制。面试主线:对象什么时候被回收、循环引用怎么破、分代为什么快。

引用计数

每个对象维护引用计数(ob_refcnt),计数归零立即回收(内存释放、析构调用):

  • 赋值、传参、入容器:计数 +1
  • del、离开作用域、出容器:计数 -1
  • 优点:实时回收,没有等待期,内存及时释放
  • 缺点:无法处理循环引用(A 引用 B、B 引用 A,计数永不归零),维护计数本身有开销
a = []      # 引用计数 1
b = a       # 2
del a       # 1
del b       # 0 → 立即回收

循环引用与标记清除

循环引用只能靠 gc 模块的标记清除(mark and sweep)

  • 从根对象(全局、栈)出发遍历可达对象,标记存活
  • 未标记的就是垃圾,清除
  • 只有可能形成循环的容器对象(list/dict/自定义类实例)才进 gc 追踪,int/str 不进
class Node:
    def __init__(self):
        self.other = None

a, b = Node(), Node()
a.other, b.other = b, a   # 互相引用, 引用计数失效
del a, b                  # 只能靠 gc 回收

weakref 打破循环:弱引用不增加引用计数,目标存活时可用,回收后返回 None。缓存、观察者模式的正确姿势。

分代回收

三代分代: 越老的对象扫描越少

  • 三代:0 代(新生)、1 代、2 代(老生)
  • 每代有阈值(如 0 代 700 次分配触发一次 gc)
  • 刚创建的对象最容易死(临时对象),只扫 0 代就能回收大部分垃圾,扫描代价小
  • 活过几轮的对象大概率继续存活,减少扫描频率
  • 这就是“分代”的收益:用最少的扫描收最多的垃圾

gc.collect() 手动触发,gc.disable() 关闭(高性能场景可能关闭后手动管理,不推荐新手)。

内存泄漏排查

  • tracemalloc:跟踪内存分配来源
  • gc.get_objects():看存活对象
  • 常见泄漏源:全局缓存只加不删、事件监听器未解绑、闭包持有大对象、循环引用配 del(gc 无法回收带 del 的循环引用对象,会进 gc.garbage)

面试追问

  1. Python 用什么回收内存? 引用计数为主:归零立即回收。循环引用靠 gc 标记清除兜底
  2. 引用计数的缺点? 循环引用无法回收,计数维护有开销。所以配分代回收
  3. 分代回收为什么快? 新生对象最容易死,只扫 0 代就能回收大部分;老对象跳过多次扫描
  4. 循环引用怎么处理? gc 的标记清除自动处理;设计上避免,用 weakref 打破引用环
  5. del 的对象为什么泄漏? 循环引用加 del 的对象 gc 无法安全回收(进 gc.garbage)。避免在 del 里做清理,用 context manager 或 weakref.finalize
Navigation

Type to search…

↑↓ navigate↵ selectEsc close