标准python垃圾回收器由两部分组成,即引用计数回收器和分代垃圾回收器(即python包中的gc module)。其中,引用计数模块不能被禁用,而GC模块可以被禁用。

引用计数算法

python中每个变量都是对象,每个对象核心都包含一个结构体PyObject:

 typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;

其中ob_refcnt变量记录了这个对象被引用的数量,当这个对象的引用被赋值给另一个变量或者这个引用被删除(如del a)。那么ob_refcnt就会随之加1或者减1。引用计数增加的场景有:

  • 赋值操作
  • 参数传递
  • 添加object到list末尾

如果某个对象的引用计数为0,那么python会立马调用函数释放内存空间,此时可能导致这个对象引用的其他对象引用计数也为0,因此一个对象消亡可能引起一连串对象内存空间被释放。

一般说来,全局变量(定义在函数,类和代码块之外的变量)随python进程退出而释放,因此引用计数永远不会减为0。局部变量(定义在函数,类和代码块之内的变量)反之。

引用计数算法原理简单明了,但是存在循环引用,线程锁和内存性能开销等问题。

分代垃圾回收算法

引用计数算法最大问题就是循环引用,这种情况下循环引用的每个对象的引用计数最终都不会归零,如下图所示:



因此,python在gc module实现了循环检测算法来解决这个问题。循环引用只可能发生在容器对象之间,因此,GC循环检测也只对容器对象使用。对于只包含常量的容器(如包含常量的tuple),GC也会把这部分排除掉。

GC将容器对象分为3代,定期清理每代的垃圾对象。新分配的对象总是在第0代,如果经历过一次垃圾回收依然存活则移入下一代。新生代的回收频率比老年代高,大部分新对象很快就会释放,因此生命周期都只是停留在第0代。这种分代回收方式可以提高GC性能,减少停顿时间。

GC进行时需要停止程序的执行以免引用关系改变。

为了确定何时可以执行垃圾回收,GC每一代维护都会count和threshold两个变量,其中count=上一次垃圾回收后分配的新对象-上一次垃圾回收后已经消亡对象(因为引用计数为0而被释放),每次对象创建时候都会比较count和threshold大小,如果count>threshold,python就会启动垃圾回收进程。

以下详细说明CPython循环检测的原理,CPython维护两个双向链表:一个是需要检测的对象关系链表(objects list),一个是存放无法访达的对象链表(Unreachable list)。

  1. 如下图所示,假设现在清理的是第0代的垃圾对象。左边的需要扫描的对象列表中link4是一个循环引用。varA是link1的外部引用,可能是其他代的对象。每个对象除了有一个引用计数变量ref count外,还维护一个gc_ref变量,初始时ref count=gc_ref.

  2. GC遍历objects list每个对象,将这个对象所引用的其他对象的gc_ref减1。
  3. GC将步骤2中gc_ref=0的对象挪到Unreachable list中,下图表示将已经处理的link3和link4移到Unreachable list,但是还没有处理link1和link2.
  4. 当GC扫描link1时候,发现gc_ref=1,是可以访达的,那么GC就会循环遍历link1引用的对象,将其标记为可以访达。因为link3经过此轮后标记为可以访达,那么就会将其从Unreachable list移到之前的list中。
  5. GC扫描完所有的对象后,就会启动垃圾回收进程,将Unreachable list中的对象清理掉。

为了性能考虑,我们应该尽量避免循环引用,可以使用weakrefmodule,weakref.ref不会增加对象的引用计数并且对象被回收后返回None。此外,可以使用gc.disable()手动关闭GC垃圾回收,这在某些特殊情况下有帮助。

参考资料

[1] Garbage collection in Python: things you need to know

[2] Python垃圾回收机制

[3] The Garbage Collector

[4] Python垃圾回收(GC)三层心法,你了解到第几层

最新文章

  1. 【Beta】Daily Scrum Meeting总结
  2. OpenLayers2.13.1知识整理
  3. GitHub 中国区前 100 名到底是什么样的人?
  4. Light OJ 1025 - The Specials Menu(动态规划-区间dp)
  5. iOS Runtime原理及使用
  6. 精通CSS :nth-child伪类
  7. 删除Checkout with Multiple Addresses
  8. 图片轮播的JS写法,通用涉及多个轮播
  9. HTML5和CSS3的学习视频
  10. HTML网页中添加音频视频动画...(转)
  11. Tango_with_django_17笔记
  12. JVM内存区域划分Eden Space、Survivor Space、Tenured Gen,Perm Gen解释
  13. 用for、while、do-while循环输出10句“好好学习,天天向上!”
  14. ZooKeeper监听机制
  15. vue-15-vuex-store的用法
  16. oracle数据库定时任务dbms_job的用法详解
  17. Python·Jupyter Notebook各种使用方法
  18. 问题1:鼠标指向导航栏li,但li中a样式未改变
  19. Java中 i++ 是线程安全的么?为什么?
  20. Spring,Hibernate 集成解决多hbm.xml文件繁多的方案

热门文章

  1. javascript jquery 推断对象为空的方式
  2. nodejs是什么
  3. leetcode 新题型----SQL,shell,system design
  4. Activity的启动模式和onNewIntent()
  5. bzoj1270 BeijingWc2008 雷涛的小猫 DP
  6. oracle创建静态监听
  7. caioj 1413 动态规划4:打鼹鼠
  8. 如何在Google Play上通过电脑下载apk
  9. ECNUOJ 2144 抗震机械制造
  10. Spring-statemachine Action不能并发执行的问题