Python内存泄漏,泄漏帧

1 投票
2 回答
1404 浏览
提问于 2025-04-16 06:32

我有一段代码,用来获取调用我函数的文件名、行号和函数名。不过,看起来好像有一些“帧”在泄漏,我不太明白为什么。这是不是让我产生了误解,实际上泄漏的地方在别的地方呢?

        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列表中的帧引用的引用图:

alt text

更新2

看起来inspect模块本身在持有这些帧的引用。这是一个来自一个活跃帧的反向引用的对象图,而不是在垃圾列表中的。

alt text

链接 在这里,因为太宽了。

有没有办法清除inspect模块?这些帧到底是被保存在哪里了呢 =\

2 个回答

1

编辑:我刚意识到我之前说错了,ff_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 来消除这个循环引用。这样就打破了循环引用。

1

我想我找到了问题所在。看起来这是一个与inspect模块和底层C代码在多线程应用中的问题。上面代码的模块是从不同的线程中导入的。而第二个图表指向了这个问题。

alt text

这里第三个节点列出的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方法。我可能走错了方向,但这些帧的上下文和我调用的那个方法相差很远。

撰写回答