Python内存泄漏,泄漏帧
我有一段代码,用来获取调用我函数的文件名、行号和函数名。不过,看起来好像有一些“帧”在泄漏,我不太明白为什么。这是不是让我产生了误解,实际上泄漏的地方在别的地方呢?
rv = "(unknown file)", 0, "(unknown function)"
for f in inspect.stack()[1:]:
if __file__ in f:
continue
else:
rv = f[1:4]
break
return rv
我并没有在任何地方保存对这个帧的引用。但我可以肯定的是,确实有帧在泄漏:
> objcallgraph.show_most_common_types() >tuple 24798 >frame 9601 >...
更新:
我的帧确实在泄漏。我按照建议使用了gc.set_debug(),发现帧慢慢地进入了gc.garbage列表。不过,这个数量远远少于通过show_most_common_types()显示的创建数量。我有个关于作用域的问题,上面提到的f
在for循环结束后不是就应该失效了吗?因为我刚刚试了这个:
for f in range(20):
l = 1
print f
结果打印出了19。所以,是不是我在for循环里的f
在泄漏呢?这是一个关于在我的gc.garbage列表中的帧引用的引用图:
更新2:
看起来inspect模块本身在持有这些帧的引用。这是一个来自一个活跃帧的反向引用的对象图,而不是在垃圾列表中的。
链接 在这里,因为太宽了。
有没有办法清除inspect模块?这些帧到底是被保存在哪里了呢 =\
2 个回答
编辑:我刚意识到我之前说错了,f
和 f_back
实际上是指向同一个地方的。我保留这个内容,希望能给其他人带来启发:
每个帧都有一个叫做 f_back 的指针,所以当你执行 f = inspect.stack()[1]
时,inspect.stack()[0][0].f_locals
(里面包含 f)现在就有了对 ...stack()[1]
的引用,而 ...stack()[1][0].f_back
又指向 ...stack()[0][0]
。这样你就创建了一个循环引用,这种情况需要垃圾回收(GC)来处理,而不是简单地通过引用计数来解决。垃圾回收并没有针对你创建对象的速度进行优化,所以你会消耗越来越多的内存。
你可以通过在函数结束时将 f
设置为 None
来消除这个循环引用。这样就打破了循环引用。
我想我找到了问题所在。看起来这是一个与inspect模块和底层C代码在多线程应用中的问题。上面代码的模块是从不同的线程中导入的。而第二个图表指向了这个问题。
这里第三个节点列出的function
是inspect.getmodule()。我没法把它全部放进去,只好裁剪了一下。
(Pdb) objgraph.at(3510928)
<cell at 0x359290: dict object at 0x3849c0>
在字典里面是所有的帧信息。
(Pdb) objgraph.at(0x3849c0)
{(<frame object at 0x97a288>, '/lib/python26.zip/logging/__init__.py'): <module 'logging' from '/lib/python26.zip/logging/__init__.py'>,
(<frame object at 0x896288>, '/lib/python26.zip/logging/__init__.py'): <module 'logging' from '/lib/python26.zip/logging/__init__.py'>,
(<frame object at 0xa621b0>, '/lib/python26.zip/logging/__init__.py'): <module 'logging' from '/lib/python26.zip/logging/__init__.py'>,
(<frame object at 0x11266e8>, '/lib/python26.zip/logging/__init__.py'): <module 'logging' from '/lib/python26.zip/logging/__init__.py'>,
...}
如果你获取所有这些帧的外部帧的话,
(Pdb) inspect.getouterframes(objgraph.at(0x97a288))
[(<frame object at 0x97a288>, '/lib/python26.zip/logging/__init__.py', 1028, 'debug', [' self._log(DEBUG, msg, args, **kwargs)\n'], 0),
(<frame object at 0x794040>, '/lib/python26.zip/logging/__init__.py', 1505, 'debug', [' root.debug(*((msg,)+args), **kwargs)\n'], 0),
(<frame object at 0x794e58>, '/mmc/src/core/controller/main.py', 1046, '__startCharge', [' self.chargeLock.release()\n'], 0),
(<frame object at 0x5c4260>, '/mmc/src/core/controller/main.py', 1420, 'watchScheduleStartChargeCondition', [' ret = self.__startCharge(0, eventCode=eventCode)\n'], 0),
(<frame object at 0x5c0dd0>, '/home/ephibian/Python2/_install/lib/python2.6/threading.py', 484, 'run', None, None),
(<frame object at 0x5c3b48>, '/home/ephibian/Python2/_install/lib/python2.6/threading.py', 532, '__bootstrap_inner', None, None),
(<frame object at 0x218170>, '/home/ephibian/Python2/_install/lib/python2.6/threading.py', 504, '__bootstrap', None, None)]
它们都指向线程中的__bootstrap方法。我可能走错了方向,但这些帧的上下文和我调用的那个方法相差很远。