首先介绍下主流的垃圾回收机制,原文在 http://www.zhihu.com/question/20018826/answer/28892543
- 引用计数(reference counting):
基本思路是为每个对象加一个计数器,记录指向这个对象的引用数量。每次有一个新的引用指向这个对象,计数器加一;反之每次有一个指向这个对象引用被置空或者指向其他对象,计数器减一。当计数器变为 0 的时候,自动删除这个对象。
引用计数的优点是 1)相对简单,不需要太多运行时(run-time)的支持,可以在原生不支持 GC 的语言里实现。2)对象会在成为垃圾的瞬间被释放,不会给正常程序的执行带来额外中断。它的死穴是循环引用,对象 A 包含一个引用指向对象 B ,同时对象 B 包含一个引用指向对象 A,计数器就抓瞎了。另外,引用计数对正常程序的执行性能有影响(每次引用赋值都要改计数器),特别是在多线程环境下(改计数器要加锁同步)。
- 标记-清扫(mark-sweep)。
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
标记-清扫没有无法处理循环引用的问题,不触发 GC 时也不影响正常程序的执行性能。但它的问题是当内存耗尽触发 GC 时,需要中断正常程序一段时间来清扫内存,在内存大对象多的时候这个中断可能很长。
- 节点复制(copying)。
基本思路是把整个内存空间一分为二,不妨记为 A 和 B。所有对象的内存在 A 中分配,当 A 塞满的时候,同样从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象复制到 B 去,然后对调 A 和 B 的角色。
相对于标记-清扫,节点复制的主要缺点是总有一半空间空闲着无法利用,另一个比较隐晦的缺点是它使用内存的方式与现有的内存换页、Cache 换入换出机制有潜在的冲突。但它有个很大的优点: 所有的对象在内存中永远都是紧密排列的,所以分配内存的任务变得极为简单,只要移动一个指针即可。对于内存分配频繁的环境来说,性能优势相当大。另外,由于不需要清扫整个内存空间,所以如果内存中存活对象很少而垃圾对象很多的话(有些语言有这个倾向),触发 GC 造成的中断会小于标记-清扫。
Python 主要是通过引用计数来进行垃圾回收,这里我写了一个简单的 c 程序来模拟一下
#include <stdio.h>
#include <stdlib.h>
#define PyDateType int
#define INT 1
#define FLOAT 2
//ref_count 引用计数器
//data_type 是数据类型,这是只是模拟,真正不是这样实现的
#define PyObject_HEAD \
int ref_count; \
PyDateType data_type;
//int 类型的
typedef struct {
PyObject_HEAD
int value;
} PyIntObject;
//float 类型的
typedef struct {
PyObject_HEAD
float value;
} PyFloatObject;
int main() {
PyIntObject *py_int_10;
py_int_10 = (PyIntObject *) malloc(sizeof(PyIntObject));
py_int_10->data_type = INT;
py_int_10->ref_count = 0;
py_int_10->value = 10;
PyIntObject *py_int_20;
py_int_20 = (PyIntObject *) malloc(sizeof(PyIntObject));
py_int_20->data_type = INT;
py_int_20->ref_count = 0;
py_int_20->value = 20;
//a 指向10
PyIntObject *a, *b;
a = py_int_10;
//增加引用计数
py_int_10->ref_count++;
//b 指向20
b = py_int_20;
py_int_20->ref_count++;
//把 a 指向20
a = py_int_20;
//减少10的引用计数 增加20的引用计数
py_int_10->ref_count--;
//释放掉10的内存空间
if(py_int_10 -> ref_count == 0){
free(py_int_10);
}
py_int_20->ref_count++;
printf("a: %d, b: %d\n", a->value, b->value);
printf("ref count: %d", py_int_20->ref_count);
return 0;
}
引用计数有个明显的缺点就是没办法处理循环引用,比如
l = []
l.append(l)
del l
这个 l 永远没办法被回收,因为它的引用计数没办法归零了。为了解决循环引用的问题,CPython特别设计了一个模块gc,其主要作用就是检查出循环引用的垃圾对象,并清除他们。该模块的实现,实际上也是引入了其余两种主流的垃圾收集技术——标记清除和分代收集。
gc
模块是 Python 对外暴露的垃圾回收机制,这个模块包含了控制垃圾回收操作的函数,还有检查垃圾回收状态的函数。
追踪引用
gc
模块里面有两个函数get_referents(*objs)
和get_referrers(*objs)
。第一个函数是return a list of objects directly referred to by any of the arguments,也就是获取引用 objs 的 objects,第二个函数是return the list of objects that directly refer to any of objs,也就是获取 objs 引用的 objects。
代码示例
import gc
import pprint
class Graph(object):
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print 'Linking nodes %s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
print
print 'three refers to:'
for r in gc.get_referents(three):
pprint.pprint(r)
输出是
$ python gc_get_referents.py
Linking nodes Graph(one).next = Graph(two)
Linking nodes Graph(two).next = Graph(three)
Linking nodes Graph(three).next = Graph(one)
three refers to:
{'name': 'three', 'next': Graph(one)}
<class '__main__.Graph'>
这个例子中,three
这个实例在__dict__
属性中保存了对它的实例字典的引用。
下面这个例子,使用Queue
对所有的引用来进行广度优先搜索,用来发现循环依赖。队列中的每个元素都是一个元组,里面包含了引用链和下一个需要检查的元素。从three
这个实例开始,然后遍历它引用的所有的东西。
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann. All rights reserved.
#
"""Show the objects with references to a given object.
"""
# end_pymotw_header
import gc
import pprint
import Queue
class Graph(object):
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print 'Linking nodes %s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
print
seen = set()
to_process = Queue.Queue()
# Start with an empty object chain and Graph three.
to_process.put(([], three))
# Look for cycles, building the object chain for each object found
# in the queue so the full cycle can be printed at the end.
while not to_process.empty():
chain, next = to_process.get()
print "get:", chain, next
chain = chain[:]
chain.append(next)
print "chain", chain
print 'Examining:', repr(next)
print "add id", id(next)
seen.add(id(next))
for r in gc.get_referents(next):
# 跳过部分没用的引用
if not (isinstance(r, basestring) or isinstance(r, type)):
print "refer to:", r
if id(r) in seen:
print
print 'Found a cycle to %s:' % r
for i, link in enumerate(chain):
print ' %d: ' % i,
pprint.pprint(link)
else:
print "put:", chain, r
to_process.put((chain, r))
print "---------------"
输出是
Linking nodes Graph(one).next = Graph(two)
Linking nodes Graph(two).next = Graph(three)
Linking nodes Graph(three).next = Graph(one)
# 初始化的状态 下一个要检查的是 three
get: [] Graph(three)
chain [Graph(three)]
Examining: Graph(three)
# 标记three 的 id 已经检查
add id 4392667024
# three 指向这个东西
refer to: {'name': 'three', 'next': Graph(one)}
# 下一个要检查的就是{'name': 'three', 'next': Graph(one)}
put: [Graph(three)] {'name': 'three', 'next': Graph(one)}
---------------
get: [Graph(three)] {'name': 'three', 'next': Graph(one)}
chain [Graph(three), {'name': 'three', 'next': Graph(one)}]
Examining: {'name': 'three', 'next': Graph(one)}
add id 140688010354752
# {'name': 'three', 'next': Graph(one)} 引用了 one
refer to: Graph(one)
put: [Graph(three), {'name': 'three', 'next': Graph(one)}] Graph(one)
---------------
get: [Graph(three), {'name': 'three', 'next': Graph(one)}] Graph(one)
chain [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one)]
Examining: Graph(one)
add id 4392615504
refer to: {'name': 'one', 'next': Graph(two)}
# 现在循环链是 three -> {'name': 'three', 'next': Graph(one)} -> one
put: [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one)] {'name': 'one', 'next': Graph(two)}
---------------
get: [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one)] {'name': 'one', 'next': Graph(two)}
chain [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one), {'name': 'one', 'next': Graph(two)}]
Examining: {'name': 'one', 'next': Graph(two)}
add id 140688010307184
refer to: Graph(two)
# 循环链变为three -> {'name': 'three', 'next': Graph(one)} -> one -> {'name': 'one', 'next': Graph(two)}
put: [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one), {'name': 'one', 'next': Graph(two)}] Graph(two)
---------------
get: [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one), {'name': 'one', 'next': Graph(two)}] Graph(two)
chain [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one), {'name': 'one', 'next': Graph(two)}, Graph(two)]
Examining: Graph(two)
add id 4392665936
refer to: {'name': 'two', 'next': Graph(three)}
# 继续增长
put: [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one), {'name': 'one', 'next': Graph(two)}, Graph(two)] {'name': 'two', 'next': Graph(three)}
---------------
get: [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one), {'name': 'one', 'next': Graph(two)}, Graph(two)] {'name': 'two', 'next': Graph(three)}
chain [Graph(three), {'name': 'three', 'next': Graph(one)}, Graph(one), {'name': 'one', 'next': Graph(two)}, Graph(two), {'name': 'two', 'next': Graph(three)}]
Examining: {'name': 'two', 'next': Graph(three)}
add id 140688010362416
refer to: Graph(three)
# 这个 id 已经出现过了,说明出现了循环引用
Found a cycle to Graph(three):
0: Graph(three)
1: {'name': 'three', 'next': Graph(one)}
2: Graph(one)
3: {'name': 'one', 'next': Graph(two)}
4: Graph(two)
5: {'name': 'two', 'next': Graph(three)}
---------------
强制进行垃圾回收
代码示例
import gc
import pprint
class Graph(object):
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print 'Linking nodes %s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
print
# Remove references to the graph nodes in this module's namespace
one = two = three = None
# Show the effect of garbage collection
for i in range(2):
print 'Collecting %d ...' % i
n = gc.collect()
print 'Unreachable objects:', n
print 'Remaining Garbage:',
pprint.pprint(gc.garbage)
print
输出
$ python gc_collect.py
Linking nodes Graph(one).next = Graph(two)
Linking nodes Graph(two).next = Graph(three)
Linking nodes Graph(three).next = Graph(one)
Collecting 0 ...
Unreachable objects: 6
Remaining Garbage:[]
Collecting 1 ...
Unreachable objects: 0
Remaining Garbage:[]
这个例子中,第一次进行垃圾回收的时候,循环引用就被打破了,除了自己,没有再引用 Graph 节点的了。collect()
函数返回了 unreachable 的 objects 的数量(不知道怎么翻译比较好,反正就是没办法再获取到这三个实例了,因为之前对它三个的引用被赋值为 None 了)。这个例子中,这个值是6,因为有3个类实例和自己的实例属性字典。
如果Graph
有__del()__
方法的话,垃圾回收就不能打破循环了。
import gc
import pprint
class Graph(object):
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print '%s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
def __del__(self):
print '%s.__del__()' % self
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
# Remove references to the graph nodes in this module's namespace
one = two = three = None
# Show the effect of garbage collection
print 'Collecting...'
n = gc.collect()
print 'Unreachable objects:', n
print 'Remaining Garbage:',
pprint.pprint(gc.garbage)
输出
$ python gc_collect_with_del.py
Graph(one).next = Graph(two)
Graph(two).next = Graph(three)
Graph(three).next = Graph(one)
Collecting...
Unreachable objects: 6
Remaining Garbage:[Graph(one), Graph(two), Graph(three)]
因为在循环中,超过一个的 object 拥有 finalizer 方法,垃圾回收机制没办法确定回收的顺序,为了安全起见,就保持原样了。
当打破循环的时候,Graph 实例就可以被回收了。
import gc
import pprint
class Graph(object):
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print 'Linking nodes %s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
def __del__(self):
print '%s.__del__()' % self
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
# Remove references to the graph nodes in this module's namespace
one = two = three = None
# Collecting now keeps the objects as uncollectable
print
print 'Collecting...'
n = gc.collect()
print 'Unreachable objects:', n
print 'Remaining Garbage:',
pprint.pprint(gc.garbage)
# Break the cycle
print
print 'Breaking the cycle'
gc.garbage[0].set_next(None)
print 'Removing references in gc.garbage'
del gc.garbage[:]
# Now the objects are removed
print
print 'Collecting...'
n = gc.collect()
print 'Unreachable objects:', n
print 'Remaining Garbage:',
pprint.pprint(gc.garbage)
输出
$ python gc_collect_break_cycle.py
Linking nodes Graph(one).next = Graph(two)
Linking nodes Graph(two).next = Graph(three)
Linking nodes Graph(three).next = Graph(one)
Collecting...
Unreachable objects: 6
Remaining Garbage:[Graph(one), Graph(two), Graph(three)]
Breaking the cycle
Linking nodes Graph(one).next = None
Removing references in gc.garbage
Graph(two).__del__()
Graph(three).__del__()
Graph(one).__del__()
Collecting...
Unreachable objects: 0
Remaining Garbage:[]
因为gc.garbage
会有一个对上次的垃圾回收的引用,这里打破循环后必须清除掉它,减少引用计数,然后回收掉。
寻找对不能被垃圾回收的 objects 的引用
这个例子创建了一个循环依赖,然后通过垃圾回收的 Graph 实例来寻找和移除对父节点的引用。
import gc
import pprint
import Queue
class Graph(object):
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print 'Linking nodes %s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
def __del__(self):
print '%s.__del__()' % self
# Construct two graph cycles
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
# Remove references to the graph nodes in this module's namespace
one = two = three = None
# Collecting now keeps the objects as uncollectable
print
print 'Collecting...'
n = gc.collect()
print 'Unreachable objects:', n
print 'Remaining Garbage:',
pprint.pprint(gc.garbage)
REFERRERS_TO_IGNORE = [ locals(), globals(), gc.garbage ]
def find_referring_graphs(obj):
print 'Looking for references to %s' % repr(obj)
referrers = (r for r in gc.get_referrers(obj)
if r not in REFERRERS_TO_IGNORE)
for ref in referrers:
if isinstance(ref, Graph):
# A graph node
yield ref
elif isinstance(ref, dict):
# An instance or other namespace dictionary
for parent in find_referring_graphs(ref):
yield parent
# Look for objects that refer to the objects that remain in
# gc.garbage.
print
print 'Clearing referrers:'
for obj in gc.garbage:
for ref in find_referring_graphs(obj):
ref.set_next(None)
del ref # remove local reference so the node can be deleted
del obj # remove local reference so the node can be deleted
# Clear references held by gc.garbage
print
print 'Clearing gc.garbage:'
del gc.garbage[:]
# Everything should have been freed this time
print
print 'Collecting...'
n = gc.collect()
print 'Unreachable objects:', n
print 'Remaining Garbage:',
pprint.pprint(gc.garbage)
Collection Thresholds and Generations
垃圾收集器维护了三个列表,每一个列表都成为”一代”。每一代的 objects 都会被检查的时候,它不是被垃圾回收就是进入了下一代,直到进入了被永久保存的的状态。
垃圾收集的频率是可以调节的,这个和 objects 的分配和回收的数量有关。当分配的数目减去释放的数目大于这一代的 threshold 的时候,就会进行垃圾收集。这个 thresholds 可以使用get_threshold()
函数看到。
import gc
print gc.get_threshold()
返回每一代的 threshold 的值
$ python gc_get_threshold.py
(700, 10, 10)
thresholds 的值可以通过set_threshold()
函数修改,这个例子在命令行中读取0代的 threshold 的值,然后然后分配 objects。
import gc
import pprint
import sys
try:
threshold = int(sys.argv[1])
except (IndexError, ValueError, TypeError):
print 'Missing or invalid threshold, using default'
threshold = 5
class MyObj(object):
def __init__(self, name):
self.name = name
print 'Created', self.name
gc.set_debug(gc.DEBUG_STATS)
gc.set_threshold(threshold, 1, 1)
print 'Thresholds:', gc.get_threshold()
print 'Clear the collector by forcing a run'
gc.collect()
print
print 'Creating objects'
objs = []
for i in range(10):
objs.append(MyObj(i))
不同的 thresholds 的值导致垃圾收集的频率发生变化,打开 debug 之后可以看到。
$ python -u gc_threshold.py 5
Thresholds: (5, 1, 1)
Clear the collector by forcing a run
gc: collecting generation 2...
gc: objects in each generation: 144 3163 0
gc: done, 0.0004s elapsed.
Creating objects
gc: collecting generation 0...
gc: objects in each generation: 7 0 3234
gc: done, 0.0000s elapsed.
Created 0
Created 1
Created 2
Created 3
Created 4
gc: collecting generation 0...
gc: objects in each generation: 6 4 3234
gc: done, 0.0000s elapsed.
Created 5
Created 6
Created 7
Created 8
Created 9
gc: collecting generation 2...
gc: objects in each generation: 5 6 3232
gc: done, 0.0004s elapsed.
threshold 的值变小可以让垃圾收集更频繁
$ python -u gc_threshold.py 2
Thresholds: (2, 1, 1)
Clear the collector by forcing a run
gc: collecting generation 2...
gc: objects in each generation: 144 3163 0
gc: done, 0.0004s elapsed.
Creating objects
gc: collecting generation 0...
gc: objects in each generation: 3 0 3234
gc: done, 0.0000s elapsed.
gc: collecting generation 0...
gc: objects in each generation: 4 3 3234
gc: done, 0.0000s elapsed.
Created 0
Created 1
gc: collecting generation 1...
gc: objects in each generation: 3 4 3234
gc: done, 0.0000s elapsed.
Created 2
Created 3
Created 4
gc: collecting generation 0...
gc: objects in each generation: 5 0 3239
gc: done, 0.0000s elapsed.
Created 5
Created 6
Created 7
gc: collecting generation 0...
gc: objects in each generation: 5 3 3239
gc: done, 0.0000s elapsed.
Created 8
Created 9
gc: collecting generation 2...
gc: objects in each generation: 2 6 3235
gc: done, 0.0004s elapsed.
调试
Python gc 的 set_debug()
可以接受一些参数来配置垃圾收集器。调试信息被输出到 stderr。
DEBUG_STATS
标志可以打开统计报告,显示每一代追踪的 objects 的数量,还有收集花费的时间。
import gc
gc.set_debug(gc.DEBUG_STATS)
gc.collect()
这个例子输出了两次独立的垃圾收集过程,一次是手动调用的时候,一次就是 Python 解释器退出的时候。
$ python gc_debug_stats.py
gc: collecting generation 2...
gc: objects in each generation: 667 2808 0
gc: done, 0.0011s elapsed.
gc: collecting generation 2...
gc: objects in each generation: 0 0 3164
gc: done, 0.0009s elapsed.
打开DEBUG_COLLECTABLE
和DEBUG_UNCOLLECTABLE
可以让垃圾回收器在检查每一个 object 的时候显示它能否被收集,你还可以同时和DEBUG_OBJECTS
一起使用,这样的话,每个 objects 被检查的时候都会输出一些信息。
import gc
flags = (gc.DEBUG_COLLECTABLE |
gc.DEBUG_UNCOLLECTABLE |
gc.DEBUG_OBJECTS
)
gc.set_debug(flags)
class Graph(object):
def __init__(self, name):
self.name = name
self.next = None
print 'Creating %s 0x%x (%s)' % (self.__class__.__name__, id(self), name)
def set_next(self, next):
print 'Linking nodes %s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
class CleanupGraph(Graph):
def __del__(self):
print '%s.__del__()' % self
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
one.set_next(two)
two.set_next(one)
# Construct another node that stands on its own
three = CleanupGraph('three')
# Construct a graph cycle with a finalizer
four = CleanupGraph('four')
five = CleanupGraph('five')
four.set_next(five)
five.set_next(four)
# Remove references to the graph nodes in this module's namespace
one = two = three = four = five = None
print
# Force a sweep
print 'Collecting'
gc.collect()
print 'Done'
输出结果可以看出来,Graph 实例one
和two
出现了循环依赖,但是仍然可以被垃圾回收,因为它们没有自己的 findlizer 方法,而且它们只有来自可以被垃圾回收的 objects 的引用。虽然CleanupGraph
有 finalizer 方法,但是three
一旦引用计数变为0还是可以被重新识别的。相比之下,four
和five
就有循环以来,而且没办法被释放。
$ python -u gc_debug_collectable_objects.py
Creating Graph 0x10045f750 (one)
Creating Graph 0x10045f790 (two)
Linking nodes Graph(one).next = Graph(two)
Linking nodes Graph(two).next = Graph(one)
Creating CleanupGraph 0x10045f7d0 (three)
Creating CleanupGraph 0x10045f810 (four)
Creating CleanupGraph 0x10045f850 (five)
Linking nodes CleanupGraph(four).next = CleanupGraph(five)
Linking nodes CleanupGraph(five).next = CleanupGraph(four)
CleanupGraph(three).__del__()
Collecting
gc: collectable <Graph 0x10045f750>
gc: collectable <Graph 0x10045f790>
gc: collectable <dict 0x100360a30>
gc: collectable <dict 0x100361cc0>
gc: uncollectable <CleanupGraph 0x10045f810>
gc: uncollectable <CleanupGraph 0x10045f850>
gc: uncollectable <dict 0x100361de0>
gc: uncollectable <dict 0x100362140>
Done
DEBUG_INSTANCES
调试标志可以用来追踪旧式类,不继承 object 的那种。
import gc
flags = (gc.DEBUG_COLLECTABLE |
gc.DEBUG_UNCOLLECTABLE |
gc.DEBUG_INSTANCES
)
gc.set_debug(flags)
class Graph:
def __init__(self, name):
self.name = name
self.next = None
print 'Creating %s 0x%x (%s)' % (self.__class__.__name__, id(self), name)
def set_next(self, next):
print 'Linking nodes %s.next = %s' % (self, next)
self.next = next
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.name)
class CleanupGraph(Graph):
def __del__(self):
print '%s.__del__()' % self
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
one.set_next(two)
two.set_next(one)
# Construct another node that stands on its own
three = CleanupGraph('three')
# Construct a graph cycle with a finalizer
four = CleanupGraph('four')
five = CleanupGraph('five')
four.set_next(five)
five.set_next(four)
# Remove references to the graph nodes in this module's namespace
one = two = three = four = five = None
print
# Force a sweep
print 'Collecting'
gc.collect()
print 'Done'
这种情况下,dict
object 包含的实例属性没有包含在输出中。
$ python -u gc_debug_collectable_instances.py
Creating Graph 0x1004687a0 (one)
Creating Graph 0x1004687e8 (two)
Linking nodes Graph(one).next = Graph(two)
Linking nodes Graph(two).next = Graph(one)
Creating CleanupGraph 0x100468878 (three)
Creating CleanupGraph 0x1004688c0 (four)
Creating CleanupGraph 0x100468908 (five)
Linking nodes CleanupGraph(four).next = CleanupGraph(five)
Linking nodes CleanupGraph(five).next = CleanupGraph(four)
CleanupGraph(three).__del__()
Collecting
gc: collectable <Graph instance at 0x1004687a0>
gc: collectable <Graph instance at 0x1004687e8>
gc: uncollectable <CleanupGraph instance at 0x1004688c0>
gc: uncollectable <CleanupGraph instance at 0x100468908>
Done