垃圾收集器与gc模块
我在阅读文档时,遇到了以下一句话让我有些困惑:
因为垃圾收集器是对Python中已经使用的引用计数的补充,所以如果你确定你的程序不会产生引用循环,可以禁用垃圾收集器。
这句话是什么意思呢?如果我禁用了垃圾收集器(gc.disable()
),然后做了这样的事情:
a = 'hi'
a = 'hello'
那么'hi'
会一直留在内存中吗?我需要自己释放内存吗?
我从那句话中理解到,垃圾收集器是一个额外的工具,专门用来处理引用循环的。如果它被禁用,内存仍然会通过对象的引用计数自动清理,但引用循环就不会被处理了。这样理解对吗?
3 个回答
在你的例子中,“hi”这个字符串不会一直留在内存里。垃圾回收器会检测到循环引用。
这里有一个简单的循环引用的例子,使用的是Python:
a = []
b = [a]
a.append(b)
在这个例子中,a
包含了 b
,而 b
又包含了 a
。如果你关闭垃圾回收器,这两个对象就会一直留在内存里。
需要注意的是,有些内置模块会导致循环引用。通常情况下,关闭垃圾回收器是不值得的。
你对文档的理解是正确的(但下面有个注意事项)。
即使关闭了垃圾回收(GC),引用计数仍然有效。换句话说,循环引用不会被解决,但如果一个对象的引用计数降到零,这个对象就会被垃圾回收。
注意事项:要知道,这个规则不适用于小字符串(和整数),因为它们在Python中处理方式不同(实际上并不会被垃圾回收)——更多细节可以参考Martijn Pieters的回答。
考虑以下代码:
import weakref
import gc
class Test(object):
pass
class Cycle(object):
def __init__(self):
self.other = None
if __name__ == '__main__':
gc.disable()
print "-- No Cycle"
t = Test()
r_t = weakref.ref(t) # Weak refs don't increment refcount
print "Before re-assign"
print r_t()
t = None
print "After re-assign"
print r_t()
print
print "-- Cycle"
c1 = Cycle()
c2 = Cycle()
c1.other = c2
c2.other = c1
r_c1 = weakref.ref(c1)
r_c2 = weakref.ref(c2)
c1 = None
c2 = None
print "After re-assign"
print r_c1()
print r_c2()
print "After run GC"
gc.collect()
print r_c1()
print r_c2()
它的输出是:
-- No Cycle
Before re-assign
<__main__.Test object at 0x101387e90> # The object exists
After re-assign
None # The object was GC'd
-- Cycle
After re-assign
<__main__.Cycle object at 0x101387e90> # The object wasn't GC'd due to the circular reference
<__main__.Cycle object at 0x101387f10>
After run GC
None # The GC was able to resolve the circular reference, and deleted the object
None
在CPython中,当一个对象的引用计数降到0时,它会立即从内存中被清除。
比如,当你把变量a
重新指向'hello'
时,之前的'hi'
这个字符串对象的引用计数就会减少。如果这个计数降到0,它就会被从内存中移除。
因此,垃圾回收器只需要处理那些相互引用的对象,这样可以确保它们的引用计数不会降到0。
字符串不能引用其他对象,所以垃圾回收器对它们不感兴趣。但是,任何可以引用其他东西的对象(比如列表、字典这样的容器类型,或者任何Python类或实例)都有可能产生循环引用:
a = [] # Ref count is 1
a.append(a) # A circular reference! Ref count is now 2
del a # Ref count is decremented to 1
垃圾回收器会检测到这些循环引用;因为没有其他东西引用a
,所以最终垃圾回收的过程会打破这个循环,让引用计数自然降到0。
顺便提一下,Python编译器会把像'hi'
和'hello'
这样的字符串字面量打包成常量,并与生成的字节码一起存储,因此这些对象总是至少有一个引用。此外,源代码中使用的字符串字面量,如果符合正则表达式[a-zA-Z0-9_]
,会被驻留;这意味着它们会被变成单例,以减少内存占用,这样其他使用相同字符串字面量的代码块就会引用同一个共享字符串。