简介

Python 中垃圾回收,以引用计数为主,标记清除和分代为辅。其中,标记清除解决引用计数里循环引用无法清除的问题,分代回收用于提升垃圾回收的效率。

引用计数

引用计数:记录对象被其他使用对象引用的次数,使用 sys.getrefcount() 获取对象的引用计数
引用计数增加:

  • 一个对象分配给一个新的名字(a=[1,2])
  • 将其放入一个容器内([].append(a))

引用计数减少:

  • del a 显示删除对象
  • 对象所在容器被销毁或者从容器中删除对象
  • 引用超出作用域或重新赋值

标记-清除

标记-清除用来解决引用计数机制产生的循环引用,进而导致内存泄漏的问题 。 循环引用只有在容器对象才会产生,比如字典,元组,列表等。

标记阶段:
遍历所有的对象,如果是可达的(reachable),也就是还有对象引用它,那么就标记该对象为可达
清除阶段:
再次遍历对象,如果发现某个对象没有标记为可达(即为Unreachable),则就将其回收

分代回收

Python将所有的对象分为年轻代(第0代)、中年代(第1代)、老年代(第2代)三代。所有的新建对象默认是 第0代对象。当在第0代的gc扫描中存活下来的对象将被移至第1代,在第1代的gc扫描中存活下来的对象将被移至第2代。

当某一代中被分配的对象与被释放的对象之差达到某一阈值时,就会触发当前一代的gc扫描。当某一代被扫描时,比它年轻的一代也会被扫描,因此,第2代的gc扫描发生时,第0,1代的gc扫描也会发生,即为全代扫描。

  1. >>> import gc
  2. >>> gc.get_threshold() ## 分代回收机制的参数阈值设置
  3. (700, 10, 10)
  • 700=新分配的对象数量-释放的对象数量,第0代gc扫描被触发
  • 第一个10:第0代gc扫描发生10次,则第1代的gc扫描被触发
  • 第二个10:第1代的gc扫描发生10次,则第2代的gc扫描被触发

参考:https://juejin.cn/post/6856235545220415496#heading-7