前言: 平时没接触过Python,但是关于垃圾回收面试时候被问到了,就学习一下为什么Python用的是引用计数法。
Python GC主要使用引用计数来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”解决容器对象可能产生的循环引用问题,通过“分代回收”以空间换时间的方法提高垃圾回收效率。
引用计数
引用计数法在对象内部维护了一个被其他对象引用数的引用计数值,当这个引用计数值为0时,说明这个对象不再被其他对象引用,就可以被回收了。
所有Python对象的头部包含了这样一个结构PyObject(相当于继承自PyObject)
struct _object {
Py_ssize_t ob_refcnt;
struct PyTypeObject *ob_type;
} PyObject;
ob_refcnt就是引用计数值
可能会出现两个对象循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。
优点: 高效;运行期没有停顿;对象有确定的生命周期;易于实现
缺点:
维护引用计数的次数和引用次数成正比,而不像标记-清除那样与回收的内存数量有关。
无法解决循环引用的问题。
标记-清除
“标记-清除”法是为了解决循环引用问题。在申请内存时,所有容器对象的头部又加上了PyGC_Head
来实现“标记-清除”机制。
typedef union _gc_head {
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
Py_ssize_t gc_refs;
} gc;
long double dummy; /* force worst-case alignment */
} PyGC_Head;
垃圾标记时,先将集合中对象的引用计数复制一份副本(以免在操作过程中破坏真实的引用计数值)。
操作这个副本,遍历对象集合,将被引用对象的引用计数副本值减1。(减1后如果副本值=0说明只被外部引用了一次)
根据引用计数副本值是否为0将集合内的对象分成两类,可达和不可达,其中不可达是可以被回收的对象。在处理了弱引用和一些其他细节之后,就可以回收不可达中的对象了。
分代回收
将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
用来表示“代”的结构体是gc_generation
, 包括了当前代链表表头、对象数量上限、当前对象数量
struct gc_generation {
PyGC_Head head;
int threshold; /* collection threshold */
int count; /* count of allocations or collections of younger
generations */
};
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。新生成的对象会被加入第0代,前面_PyObject_GC_Malloc
中省略的部分就是Python GC触发的时机。每新生成一个对象都会检查第0代有没有满,如果满了就开始进行垃圾回收。
gc模块
gc模块提供一个接口给开发者设置垃圾回收的选项。它的一个主要功能就是解决循环引用的问题。
- 项目中避免循环引用
- 引入gc模块,启动gc模块的自动清理循环引用的对象机制
- 由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
- gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,如果一定要使用该方法,同时导致了循环引用,需要在代码中显式调用gc.garbage里面的对象的__del__来打破僵局
来源:oschina
链接:https://my.oschina.net/AnniHome/blog/3192643