Python:内存泄漏调试

Posted

技术标签:

【中文标题】Python:内存泄漏调试【英文标题】:Python: Memory leak debugging 【发布时间】:2010-11-23 06:46:42 【问题描述】:

我有一个在 django 中运行的小型多线程脚本,随着时间的推移,它开始使用越来越多的内存。离开它一整天会消耗大约 6GB 的 RAM,然后我开始交换。

在http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks 之后,我认为这是最常见的类型(仅使用了 800M 内存):

(Pdb)  objgraph.show_most_common_types(limit=20)
dict                       43065
tuple                      28274
function                   7335
list                       6157
NavigableString            3479
instance                   2454
cell                       1256
weakref                    974
wrapper_descriptor         836
builtin_function_or_method 766
type                       742
getset_descriptor          562
module                     423
method_descriptor          373
classobj                   256
instancemethod             255
member_descriptor          218
property                   185
Comment                    183
__proxy__                  155

这并没有显示任何奇怪的东西。我现在应该怎么做才能帮助调试内存问题?

更新:尝试一些人们推荐的东西。我一夜之间运行了这个程序,当我开始工作时,使用了 50% * 8G == 4G 的 RAM。

(Pdb) from pympler import muppy
(Pdb) muppy.print_summary()
                                     types |   # objects |   total size
========================================== | =========== | ============
                                   unicode |      210997 |     97.64 MB
                                      list |        1547 |     88.29 MB
                                      dict |       41630 |     13.21 MB
                                       set |          50 |      8.02 MB
                                       str |      109360 |      7.11 MB
                                     tuple |       27898 |      2.29 MB
                                      code |        6907 |      1.16 MB
                                      type |         760 |    653.12 KB
                                   weakref |        1014 |     87.14 KB
                                       int |        3552 |     83.25 KB
                    function (__wrapper__) |         702 |     82.27 KB
                        wrapper_descriptor |         998 |     77.97 KB
                                      cell |        1357 |     74.21 KB
  <class 'pympler.asizeof.asizeof._Claskey |        1113 |     69.56 KB
                       function (__init__) |         574 |     67.27 KB

这不是 4G 的总和,也没有真正给我任何结构化的大数据来修复。 unicode 来自一组“完成”节点,列表看起来就像随机的weakrefs。

我没有使用 guppy,因为它需要 C 扩展,而且我没有 root,所以构建起来会很痛苦。

我使用的所有对象都没有__del__ 方法,并且查看库,它看起来不像 django 和 python-mysqldb 也一样。还有其他想法吗?

【问题讨论】:

“在 Django 中运行”?您的意思是您正在使用 Django Web 服务器进行额外的非 Web 服务后台处理?您是否考虑过将这些非网络服务的内容拆分到一个单独的进程中? 这是一个 cron 作业,它导入 Django settgings.py 并使用许多 Django ORM 功能。因此,它不是由网络服务器生成的,但仍然使用许多功能(这可能是相关的) 【参考方案1】:

见http://opensourcehacker.com/2008/03/07/debugging-django-memory-leak-with-trackrefs-and-guppy/。简短回答:如果您正在运行 django 但不是以基于 Web 请求的格式,则需要手动运行 db.reset_queries() (当然还有 DEBUG=False,正如其他人所提到的)。 Django 在网络请求后自动执行reset_queries(),但以您的格式,这永远不会发生。

【讨论】:

db.reset_queries() 为我解决了一个问题,非常感谢。【参考方案2】:

settings.py 中的 DEBUG=False 吗?

如果不是这样,Django 会很乐意存储您所做的所有加起来的 SQL 查询。

【讨论】:

哇,我知道在其中写下 django 会有所帮助。是的,我的脚本没有使用我的生产 settings.py。 尴尬。让我们看看它是否解决了内存问题。 就是这样!从大型数据库中选择时,将 DEBUG 设置为 True 确实会占用大量内存。【参考方案3】:

你试过gc.set_debug()吗?

你需要问自己一些简单的问题:

我是否使用带有__del__ 方法的对象?我绝对、明确地需要它们吗? 我可以在我的代码中获得引用循环吗?我们不能在摆脱这些物体之前打破这些圈子吗?

看,主要问题是包含__del__方法的对象循环:

import gc

class A(object):
    def __del__(self):
        print 'a deleted'
        if hasattr(self, 'b'):
            delattr(self, 'b')

class B(object):
    def __init__(self, a):
        self.a = a
    def __del__(self):
        print 'b deleted'
        del self.a


def createcycle():
    a = A()
    b = B(a)
    a.b = b
    return a, b

gc.set_debug(gc.DEBUG_LEAK)

a, b = createcycle()

# remove references
del a, b

# prints:
## gc: uncollectable <A 0x...>
## gc: uncollectable <B 0x...>
## gc: uncollectable <dict 0x...>
## gc: uncollectable <dict 0x...>
gc.collect()

# to solve this we break explicitely the cycles:
a, b = createcycle()
del a.b

del a, b

# objects are removed correctly:
## a deleted
## b deleted
gc.collect()

我真的鼓励您标记在您的应用程序中循环使用的对象/概念并关注它们的生命周期:当您不再需要它们时,我们是否有任何引用它的东西?

即使对于没有__del__ 方法的循环,我们也会遇到问题:

import gc

# class without destructor
class A(object): pass

def createcycle():
    # a -> b -> c 
    # ^         |
    # ^<--<--<--|
    a = A()
    b = A()
    a.next = b
    c = A()
    b.next = c
    c.next = a
    return a, b, b

gc.set_debug(gc.DEBUG_LEAK)

a, b, c = createcycle()
# since we have no __del__ methods, gc is able to collect the cycle:

del a, b, c
# no panic message, everything is collectable:
##gc: collectable <A 0x...>
##gc: collectable <A 0x...>
##gc: collectable <dict 0x...>
##gc: collectable <A 0x...>
##gc: collectable <dict 0x...>
##gc: collectable <dict 0x...>
gc.collect()

a, b, c = createcycle()

# but as long as we keep an exterior ref to the cycle...:
seen = dict()
seen[a] = True

# delete the cycle
del a, b, c
# nothing is collected
gc.collect()

如果您必须使用类似“seen”的字典或历史记录,请注意只保留您需要的实际数据,而不是对其进行外部引用。

我现在对set_debug 有点失望,我希望它可以配置为将数据输出到 stderr 以外的其他地方,但希望 that should change soon。

【讨论】:

gc.collect() 将所有内容作为收藏品返回,第二次调用返回 0。这意味着我没有任何循环对吗? @Paul:不,你仍然可以拥有周期。看看我给出的最后一个例子:在这里,gc.collect() 确实返回 0,并且没有打印任何内容。如果您有没有 del 方法的对象循环,gc 将保持安静。【参考方案4】:

请参阅this excellent blog post from Ned Batchelder,了解他们如何在 HP 的 Tabblo 中追踪真正的内存泄漏。很经典,值得一读。

【讨论】:

【参考方案5】:

我认为您应该使用不同的工具。显然,你得到的统计信息只是关于 GC 对象(即可能参与循环的对象);最值得注意的是,它缺少字符串。

我推荐使用Pympler;这应该会为您提供更详细的统计数据。

【讨论】:

top 显示我的应用程序使用 7% * 8GB = 560M。 pymler.muppy.print_summary() 显示大约 55M。剩下的在哪里?【参考方案6】:

您使用任何扩展程序吗?它们是内存泄漏的好地方,不会被python工具跟踪。

【讨论】:

没有扩展,但是其他人在这里磕磕绊绊的好地方。 如果使用 Django ORM,则使用扩展模块 - DB-API 数据库驱动程序。这是 MySQL 数据库吗?当前版本在使用 use_unicode=True 建立连接时已知游标内存泄漏(Django>=1.0 就是这种情况)。 是的,你是对的!我正在使用所有这些。任何已知的解决方案? 尝试使用SVN的代码,漏洞已修复,但尚未发布更新。【参考方案7】:

试试Guppy。

基本上,您需要更多信息或能够提取一些信息。 Guppy 甚至提供数据的图形表示。

【讨论】:

以上是关于Python:内存泄漏调试的主要内容,如果未能解决你的问题,请参考以下文章

Python内存泄漏[关闭]

Linux如何调试内存泄漏

Linux如何调试内存泄漏

C++调试内存泄漏

C++调试内存泄漏

C++调试内存泄漏