python垃圾回收离职_Python的垃圾回收机制

标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。如图:

首先初始所有对象标记为白色,并确定根节点对象(这些对象是不会被删除),标记它们为黑色(表示对象有效)。将有效对象引用的对象标记为灰色(表示对象可达,但它们所引用的对象还没检查),检查完灰色对象引用的对象后,将灰色标记为黑色。重复直到不存在灰色节点为止。最后白色结点都是需要清除的对象。

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 structgc_generation {4 PyGC_Head head;5 int threshold; /*collection threshold*/

6 int count; /*count of allocations or collections of younger7 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 objects2 * in containers, and is GC_REACHABLE for all tracked gc objects not in3 * 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 the24 * generation being collected, which can be recognized25 * 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 >= 035 * for all objects in containers, and is GC_REACHABLE for all tracked gc36 * objects not in containers. The ones with gc_refs > 0 are directly37 * 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, but11 * the traversal hasn't yet gotten to it. All12 * we need to do is tell move_unreachable that it's13 * 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 got19 * 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 it22 * 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 other31 * generation so we don't care about it, or move_unreachable32 * 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_REACHABLE38 || 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 in46 * unreachable have gc_refs = GC_TENTATIVELY_UNREACHABLE. All tracked47 * gc objects not in young or unreachable still have gc_refs = GC_REACHABLE.48 * All objects in young after this are directly or indirectly reachable49 * from outside the original young; and all objects in unreachable are50 * 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_refs58 * = GC_REACHABLE, and are indeed reachable (directly or indirectly)59 * from outside the young list as it was at entry. All other objects60 * from the original young "to the left" of us are in unreachable now,61 * and have gc_refs = GC_TENTATIVELY_UNREACHABLE. All objects to the62 * left of us in 'young' now have been scanned, and no objects here63 * 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 the71 * original 'young'. Mark it as such, and traverse72 * its pointers to find any other objects that may73 * be directly reachable from it. Note that the74 * call to tp_traverse may append objects to young,75 * so we have to wait until it returns to determine76 * 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 from90 * any object we've already traversed, but may be91 * reachable from an object we haven't gotten to yet.92 * visit_reachable will eventually move gc back into93 * 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 reduces12 thrashing when a *very* large list is created and13 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值