垃圾收集器和gc模块

5
我正在阅读文档,看到了以下短语,有些疑惑:
“由于收集器补充了 Python 中已经使用的引用计数,如果您确信程序不会创建引用循环,可以禁用收集器。”
这是什么意思?如果我禁用垃圾回收器(`gc.disable()`),然后执行以下操作:
a = 'hi'
a = 'hello'

'hi'会保留在内存中吗?我需要手动释放内存吗?

从这句话中我理解到,gc是专门用来捕获引用循环的额外工具,如果禁用它,则仍然使用对象的引用计数器自动清理内存,但不会管理引用循环。是这样吗?


在你的例子中,'hi'确实会保留在内存中,但试图释放它是错误的。作为字符串字面值,它将被汇编并存储为常量(在代码对象中)。但这可能不是你的问题。我建议你选择一个更无辜的例子(也许涉及列表),以转移注意力。 - user395760
如果a引用的是更复杂的东西,而不是一个简单的字符串文字,那么那个“东西”会保留在内存中吗?我不能做些什么来释放内存空间吗? - zer0uno
不,恰恰相反。字符串字面值保留在内存中与循环GC是否启用或数据的复杂性无关,字符串对象仅仅是可达的,因此甚至不会被循环GC收集。这只是因为您使用了字符串字面值作为示例。其他数据不会被静默地从其他地方引用,并在像a = [1]; a = something else的示例中变得不可达并被释放。 - user395760
3个回答

5
在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,因此最终gc进程会打破循环,自然地让引用计数降至0。
顺便提一下,Python编译器将像'hi'和'hello'这样的字符串字面值与生成的字节码一起捆绑为常量,并且始终至少有一个对这样的对象的引用。此外,在源代码中使用的字符串字面值匹配正则表达式[a-zA-Z0-9_]时,它们被interned;即成为单例以减少内存占用,因此使用相同字符串字面值的其他代码块将持有对同一共享字符串的引用。

“there is always at least one reference to such objects” 是什么意思?在第二个赋值运行之后,没有对“'hi'”的引用。 - zer0uno
@antox:代码对象具有.co_consts属性,一个元组,引用了字符串'hi' - Martijn Pieters
抱歉我的无能,但我如何查看存储在.co_consts中的内容?我需要导入一个模块吗? - zer0uno
但有些事情...你告诉我Python编译器将字符串文字捆绑为常量,并且总是至少存在一个对这种对象的引用,因此第一个问题是:即使字面字符串'hi'仅在一个位置使用,这些常量是否在程序的整个执行过程中都被存储? 第二个问题是:这种行为是否仅适用于字符串?对于其他类型的不可变对象,当它们的引用计数降至零时,它们是否会立即被回收? - zer0uno
最后一个问题:如果我禁用了垃圾回收,那么像列表或字典这样的对象(它们可能包含循环引用),当它们的计数器降至0时,它们是否会被回收?还是只能由垃圾回收处理? - zer0uno
显示剩余5条评论

1
您对文档的理解是正确的(但请注意下面的警告)。
当禁用GC时,引用计数仍然有效。换句话说,循环引用不会被解决,但如果对象的引用计数降至零,则对象将被GC。
注意:这不适用于在Python中与其他对象不同处理的小字符串(和整数)(它们实际上没有被GC) - 有关更多详细信息,请参见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

0
在你的例子中,“hi”不会保留在内存中。垃圾回收器会检测到循环引用
以下是Python中循环引用的简单示例:
a = []
b = [a]
a.append(b)

这里的a包含b,而b又包含a。如果禁用垃圾回收器,这两个对象将会一直存在于内存中。

请注意,一些内置模块会导致循环引用。通常情况下,禁用垃圾回收器并不值得。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接