我发现了exec
的问题(它发生在一个必须可以用用户编写的脚本扩展的系统中)。我可以将问题本身减少到这段代码:
def fn():
context = {}
exec '''
class test:
def __init__(self):
self.buf = '1'*1024*1024*200
x = test()''' in context
fn()
我预计在调用函数fn
之后,垃圾收集器应该释放内存。但是,Python进程仍然消耗额外的200MB内存,我完全不知道这里发生了什么以及如何手动释放分配的内存。
我怀疑在exec
中定义一个类并不是一个非常明智的想法,但首先,我想了解上面示例中出现的问题。
看起来在另一个函数中创建包装类实例可以解决问题,但有什么区别?
def fn():
context = {}
exec '''
class test:
def __init__(self):
self.buf = '1'*1024*1024*200
def f1(): x = test()
f1()
''' in context
fn()
这是我的Python解释器版本:
$ python
Python 2.7 (r27:82500, Sep 16 2010, 18:02:00)
[GCC 4.5.1 20100907 (Red Hat 4.5.1-3)] on linux2
答案 0 :(得分:5)
您看到它占用200Mb内存超过预期的原因是因为您有一个参考周期:context
是一个引用x
和test
的字典。 x
引用test
的实例,该实例引用test
。 test
有一个属性词典test.__dict__
,其中包含该类的__init__
函数。 __init__
函数依次引用它所定义的全局变量 - 这是你传递给exec
,context
的字典。
Python将为您打破这些参考周期(因为没有涉及__del__
方法),但它需要gc.collect()
才能运行。 gc.collect()
将自动运行每N次分配(由gc.set_threshold()
确定),因此“泄漏”会在某个时刻消失,但如果您希望它立即消失,您可以自己运行gc.collect()
,或者在退出函数之前自己打破参考周期。您可以通过调用context.clear()
轻松完成后者 - 但您应该意识到这会影响您在其中创建的类的所有实例。
答案 1 :(得分:0)
我不认为问题与exec
有关 - 垃圾收集器没有激活。如果您将exec
代码解压缩到主应用程序中,则两种方式都会产生与exec
相同的行为:
class test:
def __init__(self):
self.buf = '1'*1024*1024*200
x = test()
# Consumes 200MB
class test:
def __init__(self):
self.buf = '1'*1024*1024*200
def f1(): x = test()
f1()
# Memory get collected correctly
两种方法之间的区别在于,在第二种方法中,本地范围在调用f1()
时会发生变化,我认为当x
超出范围时垃圾收集器会启动该函数将控制权返回给主脚本。如果范围没有改变,那么垃圾收集器会等待until the difference between the number of allocations and the number of deallocations exceeds its threshold(在我的机器上,默认情况下阈值为700 - 运行Python 2.7)。
我们可以弄清楚发生了什么:
import sys
import gc
class test:
def __init__(self):
self.buf = '1'*1024*1024*200
x = test()
print gc.get_count()
# Prints (168, 8, 0)
因此,我们看到垃圾收集器多次启动,但由于某种原因不收集x
。如果您使用其他版本进行测试:
import sys
import gc
class test:
def __init__(self):
self.buf = '1'*1024*1024*200
def f1(): x = test()
f1()
print gc.get_count()
# Prints (172, 8, 0)
在这种情况下,我们知道它确实设法收集x
。因此,似乎在全局范围内声明x
时,它会保留一些自身的循环引用,以防止它被收集。我们总是可以使用del x
手动强制收集,但当然这并不理想。如果使用gc.get_referrers(x)
,我们将能够看到哪些对象仍然引用x
,也许这将提供如何阻止这种情况发生的线索。
我知道我并没有真正解决这个问题,但希望这能帮助你朝着正确的方向前进。我会记住这个问题,以防我稍后发现。