首页 / PYTHON / python垃圾回收机制
python垃圾回收机制
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了python垃圾回收机制,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含8737字,纯文字阅读大概需要13分钟。
内容图文
如今的高级语言如java,c#等,都採用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由。能够随意申请内存,但如同一把双刃剑,为大量内存泄露。悬空指针等bug埋下隐患。
1
|
typedef
struct_object
{
|
2
|
int
ob_refcnt;
|
3
|
struct_typeobject
*ob_type;
|
4
|
}PyObject;
|
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、标记-清除机制
标记-清除机制,顾名思义。首先标记对象(垃圾检測),然后清除垃圾(垃圾回收)。
如图1:
图1
首先初始全部对象标记为白色,并确定根节点对象(这些对象是不会被删除),标记它们为黑色(表示对象有效)。
将有效对象引用的对象标记为灰色(表示对象可达。
但它们所引用的对象还没检查)。检查完灰色对象引用的对象后,将灰色标记为黑色。反复直到不存在灰色节点为止。最后白色结点都是须要清除的对象。
2、回收对象的组织
这里所採用的高级机制作为引用计数的辅助机制,用于解决产生的循环引用问题。而循环引用仅仅会出如今“内部存在能够对其它对象引用的对象”,比方:list,class等。
为了要将这些回收对象组织起来,须要建立一个链表。自然。每一个被收集的对象内就须要多提供一些信息,以下代码是回收对象里必定出现的。
一个对象的实际结构如图2:
图2
通过PyGC_Head的指针将每一个回收对象连接起来。形成了一个链表。也就是在1里提到的初始化的全部对象。
3、分代技术
分代技术是一种典型的以空间换时间的技术,这也正是java里的关键技术。这样的思想简单点说就是:对象存在时间越长,越可能不是垃圾。应该越少去收集。
这种思想,能够降低标记-清除机制所带来的额外操作。分代就是将回收对象分成数个代,每一个代就是一个链表(集合),代进行标记-清除的时间与代内对象
存活时间成正比例关系。
从上面代码能够看出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是一个外部引用。
图1
对于这样一个链表,我们怎样得出根节点呢。
python里是在引用计数的基础上又提出一个有效引用计数的概念。
顾名思义,有效引用计数就是去除循环引用后的计数。
以下是计算有效引用计数的相关代码:
01
|
/*
Set all gc_refs = ob_refcnt. After this, gc_refs is > 0 for all objects
|
02
|
*
in containers, and is GC_REACHABLE for all tracked gc objects not in
|
03
|
*
containers.
|
04
|
*/
|
05
|
static
void
|
06
|
update_refs(PyGC_Head
*containers)
|
07
|
{
|
08
|
PyGC_Head
*gc = containers->gc.gc_next;
|
09
|
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对象。
标记的方法就是中里的标记思路,代码例如以下:
001
|
/*
A traversal callback for move_unreachable. */
|
002
|
static
int
|
003
|
visit_reachable(PyObject
*op, PyGC_Head *reachable)
|
004
|
{
|
005
|
if
(PyObject_IS_GC(op))
{
|
006
|
PyGC_Head
*gc = AS_GC(op);
|
007
|
const
Py_ssize_t
gc_refs = gc->gc.gc_refs;
|
008
|
009
|
if
(gc_refs
== 0) {
|
010
|
/*
This is in move_unreachable‘s ‘young‘ list, but
|
011
|
*
the traversal hasn‘t yet gotten to it. All
|
012
|
*
we need to do is tell move_unreachable that it‘s
|
013
|
*
reachable.
|
014
|
*/
|
015
|
gc->gc.gc_refs
= 1;
|
016
|
}
|
017
|
else
if
(gc_refs
== GC_TENTATIVELY_UNREACHABLE) {
|
018
|
/*
This had gc_refs = 0 when move_unreachable got
|
019
|
*
to it, but turns out it‘s reachable after all.
|
020
|
*
Move it back to move_unreachable‘s ‘young‘ list,
|
021
|
*
and move_unreachable will eventually get to it
|
022
|
*
again.
|
023
|
*/
|
024
|
gc_list_move(gc,
reachable);
|
025
|
gc->gc.gc_refs
= 1;
|
026
|
}
|
027
|
/*
Else there‘s nothing to do.
|
028
|
*
If gc_refs > 0, it must be in move_unreachable‘s ‘young‘
|
029
|
*
list, and move_unreachable will eventually get to it.
|
030
|
*
If gc_refs == GC_REACHABLE, it‘s either in some other
|
031
|
*
generation so we don‘t care about it, or move_unreachable
|
032
|
*
already dealt with it.
|
033
|
*
If gc_refs == GC_UNTRACKED, it must be ignored.
|
034
|
*/
|
035
|
else
{
|
036
|
assert
(gc_refs
> 0
|
037
|
||
gc_refs == GC_REACHABLE
|
038
|
||
gc_refs == GC_UNTRACKED);
|
039
|
}
|
040
|
}
|
041
|
return
0;
|
042
|
}
|
043
|
044
|
/*
Move the unreachable objects from young to unreachable. After this,
|
045
|
*
all objects in young have gc_refs = GC_REACHABLE, and all objects in
|
046
|
*
unreachable have gc_refs = GC_TENTATIVELY_UNREACHABLE. All tracked
|
047
|
*
gc objects not in young or unreachable still have gc_refs = GC_REACHABLE.
|
048
|
*
All objects in young after this are directly or indirectly reachable
|
049
|
*
from outside the original young; and all objects in unreachable are
|
050
|
*
not.
|
051
|
*/
|
052
|
static
void
|
053
|
move_unreachable(PyGC_Head
*young, PyGC_Head *unreachable)
|
054
|
{
|
055
|
PyGC_Head
*gc = young->gc.gc_next;
|
056
|
057
|
/*
Invariants: all objects "to the left" of us in young have gc_refs
|
058
|
*
= GC_REACHABLE, and are indeed reachable (directly or indirectly)
|
059
|
*
from outside the young list as it was at entry. All other objects
|
060
|
*
from the original young "to the left" of us are in unreachable now,
|
061
|
*
and have gc_refs = GC_TENTATIVELY_UNREACHABLE. All objects to the
|
062
|
*
left of us in ‘young‘ now have been scanned, and no objects here
|
063
|
*
or to the right have been scanned yet.
|
064
|
*/
|
065
|
066
|
while
(gc
!= young) {
|
067
|
PyGC_Head
*next;
|
068
|
069
|
if
(gc->gc.gc_refs)
{
|
070
|
/*
gc is definitely reachable from outside the
|
071
|
*
original ‘young‘. Mark it as such, and traverse
|
072
|
*
its pointers to find any other objects that may
|
073
|
*
be directly reachable from it. Note that the
|
074
|
*
call to tp_traverse may append objects to young,
|
075
|
*
so we have to wait until it returns to determine
|
076
|
*
the next object to visit.
|
077
|
*/
|
078
|
PyObject
*op = FROM_GC(gc);
|
079
|
traverseproc
traverse = Py_TYPE(op)->tp_traverse;
|
080
|
assert
(gc->gc.gc_refs
> 0);
|
081
|
gc->gc.gc_refs
= GC_REACHABLE;
|
082
|
(
void
)
traverse(op,
|
083
|
(visitproc)visit_reachable,
|
084
|
(
void
*)young);
|
085
|
next
= gc->gc.gc_next;
|
086
|
}
|
087
|
else
{
|
088
|
/*
This *may* be unreachable. To make progress,
|
089
|
*
assume it is. gc isn‘t directly reachable from
|
090
|
*
any object we‘ve already traversed, but may be
|
091
|
*
reachable from an object we haven‘t gotten to yet.
|
092
|
*
visit_reachable will eventually move gc back into
|
093
|
*
young if that‘s so, and we‘ll see it again.
|
094
|
*/
|
095
|
next
= gc->gc.gc_next;
|
096
|
gc_list_move(gc,
unreachable);
|
097
|
gc->gc.gc_refs
= GC_TENTATIVELY_UNREACHABLE;
|
098
|
}
|
099
|
gc
= next;
|
100
|
}
|
101
|
}
|
标记之后。链表如上图。
4、垃圾回收
回收的过程,就是销毁不可达链表内对象。以下代码就是list的清除方法:
01
|
/*
Methods */
|
02
|
03
|
static
void
|
04
|
list_dealloc(PyListObject
*op)
|
05
|
{
|
06
|
Py_ssize_t
i;
|
07
|
PyObject_GC_UnTrack(op);
|
08
|
Py_TRASHCAN_SAFE_BEGIN(op)
|
09
|
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
|
}
|
内容总结
以上是互联网集市为您收集整理的python垃圾回收机制全部内容,希望文章能够帮你解决python垃圾回收机制所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。