python垃圾回收机制 概况

来源:互联网 发布:java中构造方法 编辑:程序博客网 时间:2024/06/04 20:15
 突然被人问到,了不了解python的gc(垃圾回收)机制,当时还真是一头雾水,今天参照着《python源码剖析》细细研究了一下,一下是鄙人的总结:(至少适用:python2.5-2.7)

  python采取基于引用计数的垃圾回收机制,此机制也是当前最简单、最直观的垃圾回收技术,只要某个对象的引用计数为零,则就消除该对象,回收内存。然而,这种机制存在一种致命的弱点,不能处理循环引用的情况。如,python中的两个list对象,a,b,a引用b,b引用a,除了相互引用之外,没有其他外部变量引用a或者b,则可以认为这两个变量是垃圾(可以回收的),但其引用计数却不为零,所以最基本的引用计数gc机制不能处理这种情况,而python的gc机制大部分的工作都是用来处理这种循环引用的情况的。
为了解决这个致命弱点,python引入了其他的垃圾收集技术。一般的垃圾收集包括两个过程:垃圾检测和垃圾回收,而在Python中垃圾检测采用了标记-清除技术,回收阶段则使用了分代回收技术。接下来先简要说明这两种技术。
  标记-清除技术:这种技术也遵循垃圾收集机制的通用特征:先检测,再清除。这种技术的实施过程简要如下:1)寻找根对象集合 ,利用有效利用计数,可以打破循环引用计数 2)从根对象集合出发,寻找这些对象的每一个引用如A,则A为可达的(reachable),可达对象不可删除;剩下的对象为不可达对象(unreachable),这些对象可删除,这就是垃圾检测阶段,此处采用了三色标记处理,黑色代表根节点,灰色代表中层节点,白色代表叶子节点也就是可删除的节点。 3)垃圾检测结束后,最终形成一张有向无环图。保留非叶子节点,回收叶子节点。

  分代回收技术:某个对象经过垃圾收集处理的次数越多,且仍存活,则这个对象越不像是空闲对象,即活得越长的对象越不应该被收集。python中将所有内存块分成了三代,其实就是添加了额外表头信息的三个链表,其数据结构为:

struct gc_generation{    PyGC_Head head;    int threshold;/*某一代所允许的收集的最大对象数目*/    int count;/*此链中可收集的对象数目*/};

所有新创建的对象都会被添加到_PyGC_Generation0(即第0代链表中,因为新创建的对象最可能是不可达对象)。若某个链表中所受即的对象数目超过了其对应的threshold时,具体的阙值可以去obgcmodule.c中去查看,则激活对于此代(记为n)链表和比n小的代链表中对象的垃圾收集处理工作,即先检测再回收。

  分代链表中存的是什么?存的是python创建的可监控容器(container)对象。像int,string这些类型的对象不可能包含对其他对象的引用(object对象)除外,所以不会存在循环引用的问题,而如列表,元组,字典,集合这些内建对象和自定义的类的实例则可能存在循环引用问题,所以python处理循环引用问题时,主要是处理这些对象,可以大大加快处理效率。可监控容器对象包括一个PyGC_head首部,和contianer对象自身的数据,包括一个PyObject首部和数据信息。其中

typedef union _gc_head{    strutc{    union _gc_head *next;    union _gc_head *prev;    int gc_refs;    }gc;    long double dummy;}PyGC_head

正是有了这样的头部,python源码中利用_PyObject_GC_TRACK()函数将某个可监控对象加入到某个代链表中,利用_PyObject_GC_UNTRACK()方法将某个可监控对象从其所属代链表中剔除。

  对于某一代链表,还要进行前面提到的标记操作,最后才是清除操作。
 
  python的GC流程大致如下:分代->标记->清除

  对于自建类中存在__del__方法的情况,python创建了finalizers列表,专门收集这些类的实例对象,清除不掉。这种情况,只能在自己写代码时,不要在自定义类中添加__del__方法了,以免造成内存泄露问题。

参考:陈儒,《python源码分析》


0 0
原创粉丝点击