Python的垃圾回收机制
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python的垃圾回收机制相关的知识,希望对你有一定的参考价值。
Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题。通过“分代回收”(generation collection)以空间换取时间来进一步提高垃圾回收的效率。
1 typedef struct_object { 2 int ob_refcnt; 3 struct_typeobject *ob_type; 4 }PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少
1 #define Py_INCREF(op) ((op)->ob_refcnt++) //增加计数 2 #define Py_DECREF(op) \ //减少计数 3 if (--(op)->ob_refcnt != 0) 4 ; 5 else 6 __Py_Dealloc((PyObject *)(op))
1 list1 = [] 2 list2 = [] 3 list1.append(list2) 4 list2.append(list1)
上面说到python里回收机制是以引用计数为主,标记-清除和分代收集两种机制为辅。
1、标记-清除机制
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。如图:
首先初始所有对象标记为白色,并确定根节点对象(这些对象是不会被删除),标记它们为黑色(表示对象有效)。将有效对象引用的对象标记为灰色(表示对象可达,但它们所引用的对象还没检查),检查完灰色对象引用的对象后,将灰色标记为黑色。重复直到不存在灰色节点为止。最后白色结点都是需要清除的对象。
2、回收对象的组织
这里所采用的高级机制作为引用计数的辅助机制,用于解决产生的循环引用问题。而循环引用只会出现在“内部存在可以对其他对象引用的对象”,比如:list,class等。
为了要将这些回收对象组织起来,需要建立一个链表。自然,每个被收集的对象内就需要多提供一些信息,下面代码是回收对象里必然出现的。
1 /* GC information is stored BEFORE the object structure. */ 2 typedef union _gc_head { 3 struct { 4 union _gc_head *gc_next; 5 union _gc_head *gc_prev; 6 Py_ssize_t gc_refs; 7 } gc; 8 long double dummy; /* force worst-case alignment */ 9 } PyGC_Head;一个对象的实际结构如图所示:
通过PyGC_Head的指针将每个回收对象连接起来,形成了一个链表,也就是在1里提到的初始化的所有对象。
3、分代技术
分代技术是一种典型的以空间换时间的技术,这也正是java里的关键技术。这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。
这样的思想,可以减少标记-清除机制所带来的额外操作。分代就是将回收对象分成数个代,每个代就是一个链表(集合),代进行标记-清除的时间与代内对象
存活时间成正比例关系
1 /*** Global GC state ***/ 2 3 struct gc_generation { 4 PyGC_Head head; 5 int threshold; /* collection threshold */ 6 int count; /* count of allocations or collections of younger 7 generations */ 8 };//每个代的结构 9 10 #define NUM_GENERATIONS 3//代的个数 11 #define GEN_HEAD(n) (&generations[n].head) 12 13 /* linked lists of container objects */ 14 static struct gc_generation generations[NUM_GENERATIONS] = { 15 /* PyGC_Head, threshold, count */ 16 {{{GEN_HEAD(0), GEN_HEAD(0), 0}}, 700, 0}, 17 {{{GEN_HEAD(1), GEN_HEAD(1), 0}}, 10, 0}, 18 {{{GEN_HEAD(2), GEN_HEAD(2), 0}}, 10, 0}, 19 }; 20 21 PyGC_Head *_PyGC_generation0 = GEN_HEAD(0);从上面代码可以看出python里一共有三代,每个代的threshold值表示该代最多容纳对象的个数。默认情况下,当0代超过700,或1,2代超过10,垃圾回收机制将触发。
0代触发将清理所有三代,1代触发会清理1,2代,2代触发后只会清理自己。
这篇算是一个完整的收集流程:链表建立,确定根节点,垃圾标记,垃圾回收~
1、链表建立
首先,中里在分代技术说过:0代触发将清理所有三代,1代触发会清理1,2代,2代触发后只会清理自己。在清理0代时,会将三个链表(代)链接起来,清理1代的时,会链接1,2两代。在后面三步,都是针对的这个建立之后的链表。
2、确定根节点
图1为一个例子。list1与list2循环引用,list3与list4循环引用。a是一个外部引用。
对于这样一个链表,我们如何得出根节点呢。python里是在引用计数的基础上又提出一个有效引用计数的概念。顾名思义,有效引用计数就是去除循环引用后的计数。
下面是计算有效引用计数的相关代码:
1 /* Set all gc_refs = ob_refcnt. After this, gc_refs is > 0 for all objects 2 * in containers, and is GC_REACHABLE for all tracked gc objects not in 3 * containers. 4 */ 5 static void 6 update_refs(PyGC_Head *containers) 7 { 8 PyGC_Head *gc = containers->gc.gc_next; 9 for (; gc != containers; gc = gc->gc.gc_next) { 10 assert(gc->gc.gc_refs == GC_REACHABLE); 11 gc->gc.gc_refs = Py_REFCNT(FROM_GC(gc)); 12 assert(gc->gc.gc_refs != 0); 13 } 14 } 15 16 /* A traversal callback for subtract_refs. */ 17 static int 18 visit_decref(PyObject *op, void *data) 19 { 20 assert(op != NULL); 21 if (PyObject_IS_GC(op)) { 22 PyGC_Head *gc = AS_GC(op); 23 /* We‘re only interested in gc_refs for objects in the 24 * generation being collected, which can be recognized 25 * because only they have positive gc_refs. 26 */ 27 assert(gc->gc.gc_refs != 0); /* else refcount was too small */ 28 if (gc->gc.gc_refs > 0) 29 gc->gc.gc_refs--; 30 } 31 return 0; 32 } 33 34 /* Subtract internal references from gc_refs. After this, gc_refs is >= 0 35 * for all objects in containers, and is GC_REACHABLE for all tracked gc 36 * objects not in containers. The ones with gc_refs > 0 are directly 37 * reachable from outside containers, and so can‘t be collected. 38 */ 39 static void 40 subtract_refs(PyGC_Head *containers) 41 { 42 traverseproc traverse; 43 PyGC_Head *gc = containers->gc.gc_next; 44 for (; gc != containers; gc=gc->gc.gc_next) { 45 traverse = Py_TYPE(FROM_GC(gc))->tp_traverse; 46 (void) traverse(FROM_GC(gc), 47 (visitproc)visit_decref, 48 NULL); 49 } 50 }update_refs函数里建立了一个引用的副本。
visit_decref函数对引用的副本减1,subtract_refs函数里traverse的作用是遍历对象里的每一个引用,执行visit_decref操作。
最后,链表内引用计数副本非0的对象,就是根节点了。
说明:
1、为什么要建立引用副本?
答:这个过程是寻找根节点的过程,在这个时候修改计数不合适。subtract_refs会对对象的引用对象执行visit_decref操作。如果链表内对象引用了链表外对象,那么链表外对象计数会减1,显然,很有可能这个对象会被回收,而回收机制里根本不应该对非回收对象处理。
2、traverse的疑问(未解决)?
答:一开始,有个疑问。上面例子里,subtract_refs函数中处理完list1结果应该如下:
然后gc指向list2,此时list2的副本(为0)不会减少,但是list2对list1还是存在实际上的引用,那么list1副本会减1吗?显然,如果减1就出问题了。
所以list1为0时,traverse根本不会再去处理list1这些引用(或者说,list2对list1名义上不存在引用了)。
此时,又有一个问题,如果存在一个外部对象b,对list2引用,subtract_refs函数中处理完list1后,如下图:
当subtract_refs函数中遍历到list2时,list2的副本还会减1吗?显然traverse的作用还是没有理解。
3、垃圾标记
接下来,python建立两条链表,一条存放根节点,以及根节点的引用对象。另外一条存放unreachable对象。
标记的方法就是中里的标记思路,代码如下:
1 /* A traversal callback for move_unreachable. */ 2 static int 3 visit_reachable(PyObject *op, PyGC_Head *reachable) 4 { 5 if (PyObject_IS_GC(op)) { 6 PyGC_Head *gc = AS_GC(op); 7 const Py_ssize_t gc_refs = gc->gc.gc_refs; 8 9 if (gc_refs == 0) { 10 /* This is in move_unreachable‘s ‘young‘ list, but 11 * the traversal hasn‘t yet gotten to it. All 12 * we need to do is tell move_unreachable that it‘s 13 * reachable. 14 */ 15 gc->gc.gc_refs = 1; 16 } 17 else if (gc_refs == GC_TENTATIVELY_UNREACHABLE) { 18 /* This had gc_refs = 0 when move_unreachable got 19 * to it, but turns out it‘s reachable after all. 20 * Move it back to move_unreachable‘s ‘young‘ list, 21 * and move_unreachable will eventually get to it 22 * again. 23 */ 24 gc_list_move(gc, reachable); 25 gc->gc.gc_refs = 1; 26 } 27 /* Else there‘s nothing to do. 28 * If gc_refs > 0, it must be in move_unreachable‘s ‘young‘ 29 * list, and move_unreachable will eventually get to it. 30 * If gc_refs == GC_REACHABLE, it‘s either in some other 31 * generation so we don‘t care about it, or move_unreachable 32 * already dealt with it. 33 * If gc_refs == GC_UNTRACKED, it must be ignored. 34 */ 35 else { 36 assert(gc_refs > 0 37 || gc_refs == GC_REACHABLE 38 || gc_refs == GC_UNTRACKED); 39 } 40 } 41 return 0; 42 } 43 44 /* Move the unreachable objects from young to unreachable. After this, 45 * all objects in young have gc_refs = GC_REACHABLE, and all objects in 46 * unreachable have gc_refs = GC_TENTATIVELY_UNREACHABLE. All tracked 47 * gc objects not in young or unreachable still have gc_refs = GC_REACHABLE. 48 * All objects in young after this are directly or indirectly reachable 49 * from outside the original young; and all objects in unreachable are 50 * not. 51 */ 52 static void 53 move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) 54 { 55 PyGC_Head *gc = young->gc.gc_next; 56 57 /* Invariants: all objects "to the left" of us in young have gc_refs 58 * = GC_REACHABLE, and are indeed reachable (directly or indirectly) 59 * from outside the young list as it was at entry. All other objects 60 * from the original young "to the left" of us are in unreachable now, 61 * and have gc_refs = GC_TENTATIVELY_UNREACHABLE. All objects to the 62 * left of us in ‘young‘ now have been scanned, and no objects here 63 * or to the right have been scanned yet. 64 */ 65 66 while (gc != young) { 67 PyGC_Head *next; 68 69 if (gc->gc.gc_refs) { 70 /* gc is definitely reachable from outside the 71 * original ‘young‘. Mark it as such, and traverse 72 * its pointers to find any other objects that may 73 * be directly reachable from it. Note that the 74 * call to tp_traverse may append objects to young, 75 * so we have to wait until it returns to determine 76 * the next object to visit. 77 */ 78 PyObject *op = FROM_GC(gc); 79 traverseproc traverse = Py_TYPE(op)->tp_traverse; 80 assert(gc->gc.gc_refs > 0); 81 gc->gc.gc_refs = GC_REACHABLE; 82 (void) traverse(op, 83 (visitproc)visit_reachable, 84 (void *)young); 85 next = gc->gc.gc_next; 86 } 87 else { 88 /* This *may* be unreachable. To make progress, 89 * assume it is. gc isn‘t directly reachable from 90 * any object we‘ve already traversed, but may be 91 * reachable from an object we haven‘t gotten to yet. 92 * visit_reachable will eventually move gc back into 93 * young if that‘s so, and we‘ll see it again. 94 */ 95 next = gc->gc.gc_next; 96 gc_list_move(gc, unreachable); 97 gc->gc.gc_refs = GC_TENTATIVELY_UNREACHABLE; 98 } 99 gc = next; 100 } 101 }标记之后,链表如上图。
4、垃圾回收
回收的过程,就是销毁不可达链表内对象。下面代码就是list的清除方法:
1 /* Methods */ 2 3 static void 4 list_dealloc(PyListObject *op) 5 { 6 Py_ssize_t i; 7 PyObject_GC_UnTrack(op); 8 Py_TRASHCAN_SAFE_BEGIN(op) 9 if (op->ob_item != NULL) { 10 /* Do it backwards, for Christian Tismer. 11 There‘s a simple test case where somehow this reduces 12 thrashing when a *very* large list is created and 13 immediately deleted. */ 14 i = Py_SIZE(op); 15 while (--i >= 0) { 16 Py_XDECREF(op->ob_item[i]); 17 } 18 PyMem_FREE(op->ob_item); 19 } 20 if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) 21 free_list[numfree++] = op; 22 else 23 Py_TYPE(op)->tp_free((PyObject *)op); 24 Py_TRASHCAN_SAFE_END(op) 25 }转自:https://my.oschina.net/hebianxizao/blog/59896
以上是关于Python的垃圾回收机制的主要内容,如果未能解决你的问题,请参考以下文章