Python: 内存泄漏调试

29

我在Django上运行了一个小的多线程脚本,随着时间的推移,它开始使用越来越多的内存。让它运行一整天后,RAM使用量会达到约6GB,就会开始交换。

根据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来自“完成”节点的set(),列表看起来只是随机的weakref

我没有使用guppy,因为它需要C扩展,而且我没有root权限,因此构建过程很痛苦。

我使用的任何对象都没有__del__方法,并且查看库时,django和python-mysqldb似乎也没有。还有其他想法吗?


“在Django中运行”?你的意思是你正在使用Django Web服务器进行额外的非Web服务后台处理吗?你考虑过将这些非Web服务的内容拆分为单独的进程吗? - S.Lott
2
这是一个 cron 作业,它导入 Django 的 settgings.py 并使用许多 Django ORM 功能。因此,它不是由 Web 服务器生成的,但仍然使用许多功能(可能是相关的)。 - Paul Tarjan
7个回答

32

2
db.reset_queries()为我解决了一个问题,非常感谢。 - endre

21

settings.py文件中DEBUG=False了吗?

如果没有,Django会愉快地存储您所做的所有SQL查询,这会增加负担。


2
哇,我知道在那里写上django这个词会有帮助。是的,我的脚本没有使用我的生产settings.py文件。尴尬。看看它是否可以解决内存问题。 - Paul Tarjan
这就是问题所在!当 DEBUG 设置为 True 时,从大型数据库中选择数据会占用大量内存。 - Jernej Jerin

6

你尝试过使用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 以外的其他位置输出数据,但幸运的是,这应该很快会改变


gc.collect() 返回所有可回收的对象,并在第二次调用时返回 0。这意味着我没有任何循环引用,对吗? - Paul Tarjan
@Paul:不,你仍然可以有循环。看看我最后给出的例子:在这里,gc.collect()确实返回0,并且没有打印任何内容。如果您有没有__del__方法的对象循环,gc将保持安静。 - Nicolas Dumazet

6

1

我认为你应该使用不同的工具。显然,你得到的统计数据仅涉及GC对象(即可能参与循环的对象);最重要的是,它缺少字符串。

我建议使用Pympler;这应该为你提供更详细的统计信息。


顶部显示我的应用程序使用了7% * 8GB = 560M。 pympler.muppy.print_summary()显示大约55M。剩下的在哪里? - Paul Tarjan

1

你使用任何扩展吗?它们是内存泄漏的好地方,并且不会被 Python 工具跟踪。


没有扩展,但这是其他人在这里遇到问题的好地方。 - Paul Tarjan
如果您使用Django ORM,则使用扩展模块-DB-API数据库驱动程序。这是MySQLdb吗?当前版本在使用use_unicode = True(对于Django> = 1.0的情况)建立连接时具有已知的游标内存泄漏问题。 - zgoda
是的,你说得一点没错!我正在使用所有这些。有没有已知的解决方案? - Paul Tarjan
尝试使用SVN中的代码,漏洞已经修复,但更新尚未发布。 - zgoda

0

试试Guppy

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接