【问题标题】:Garbage Collector and gc module垃圾收集器和 gc 模块
【发布时间】:2014-08-26 13:42:10
【问题描述】:

当我对以下短语产生疑问时,我正在阅读文档:

由于收集器补充了 Python 中已经使用的引用计数,如果您确定您的程序不会创建引用循环,则可以禁用收集器。

这是什么意思?如果我禁用垃圾收集器 (gc.disable()) 并执行以下操作:

a = 'hi'
a = 'hello'

'hi' 会留在内存中吗?我需要自己释放内存吗?

我从那句话中了解到,gc 是一个额外的工具,专门用于捕获引用周期,如果禁用它,内存仍会使用对象的引用计数器自动清理,但不会管理引用周期.对吗?

【问题讨论】:

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

标签: python garbage-collection


【解决方案1】:

在 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')与生成的字节码捆绑为 constants,因此,总是至少有一个对此类对象的引用。此外,源代码中使用的匹配正则表达式[a-zA-Z0-9_] 的字符串文字是interned;制作成单例以减少内存占用,因此使用相同字符串文字的其他代码块将持有对相同共享字符串的引用。

【讨论】:

  • “总是至少有一个对此类对象的引用”是什么意思?运行第二次分配后,没有对'hi'....的引用。
  • @antox:代码对象有一个.co_consts属性,一个元组,引用'hi'字符串。
  • 很抱歉我的无能,但我怎么能看到它存储在.co_consts 中?我需要导入模块吗?
  • 查看inspect module documentation 获取对象内部属性的方便表。函数具有引用其代码对象的.func_code 属性。框架和生成器对象也有代码对象引用。您可以使用compile function 动态创建代码对象,dis module 可以让您自省字节码(如果您传入其他内容,dis.dis() 将为您找到代码对象)。
  • 然而有些东西......你告诉我 Python 编译器将字符串文字捆绑为常量,因此总是至少有一个对此类对象的引用,所以第一个问题是:这些常量是否在所有即使文字字符串'hi' 只在一个地方使用,程序的执行?第二个问题是:这种行为是否只保留给字符串?对于其他类型的对象,例如不可变对象,当它们的引用计数器降至零时,它们会立即回收吗?
【解决方案2】:

您对文档的理解是正确的(但请参阅下面的警告)。

当 GC 被禁用时,引用计数仍然有效。换句话说,循环引用不会被解析,但是如果一个对象的引用计数下降到零,这个对象就会被 GC'd。

警告:请注意,这不适用于与 Python 中的其他对象不同处理的小字符串(和整数)(它们并不是真正的 GC'd)——有关更多详细信息,请参阅 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

【讨论】:

    【解决方案3】:

    在您的示例中,“hi”不会保留在内存中。垃圾收集器检测到Circular references

    下面是一个简单的python循环引用示例:

    a = []
    b = [a]
    a.append(b)
    

    这里a 包含bb 包含a。如果禁用垃圾收集器,这两个对象将保留在内存中。

    请注意,某些内置模块会导致循环引用。而且通常不值得禁用它。

    【讨论】:

      猜你喜欢
      • 2023-03-30
      • 1970-01-01
      • 2011-07-16
      • 1970-01-01
      • 2015-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多