Python:垃圾收集器的行为

mir*_*e2k 9 python memory django garbage-collection

我有一个Django应用程序,展示了一些奇怪的垃圾收集行为.有一个视图特别是每次调用时都会显着增加VM大小 - 达到某个限制,此时使用量会再次下降.问题是,在达到这一点之前需要相当长的时间,实际上运行我的应用程序的虚拟机没有足够的内存供所有FCGI进程占用尽可能多的内存.

我花了最近两天的时间来研究这个并学习Python垃圾收集,我想我现在明白了现在发生了什么 - 大部分时间.使用时

gc.set_debug(gc.DEBUG_STATS)
Run Code Online (Sandbox Code Playgroud)

然后对于单个请求,我看到以下输出:

>>> c = django.test.Client()
>>> c.get('/the/view/')
gc: collecting generation 0...
gc: objects in each generation: 724 5748 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 731 6460 147341
gc: done.
[...more of the same...]    
gc: collecting generation 1...
gc: objects in each generation: 718 8577 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 714 0 156614
gc: done.
[...more of the same...]
gc: collecting generation 0...
gc: objects in each generation: 715 5578 156612
gc: done.
Run Code Online (Sandbox Code Playgroud)

基本上,分配了大量的对象,但是最初被移动到第1代,当gen 1在同一请求期间被清空时,它们被移动到第2代.如果我之后做了手动gc.collect(2),他们被删除了.并且,正如我所提到的,当下一次自动第2代扫描发生时也会被删除,如果我理解正确的话,在这种情况下,就像每10个请求一样(此时应用程序需要大约150MB).

好吧,所以最初我认为在处理一个请求时可能会有一些循环引用,这会阻止在处理该请求时收集任何这些对象.但是,我花了好几个小时尝试使用pympler.muppy和objgraph,在请求处理之后和调试之间找到一个,并且似乎没有.相反,似乎在请求期间创建的14.000个对象都在一个请求全局对象的引用链中,即一旦请求消失,它们就可以被释放.

无论如何,这是我试图解释它.但是,如果这是真的并且确实没有循环依赖,那么一旦任何导致它们被保持的请求对象消失,整个对象树就不会被释放,而不涉及垃圾收集器,纯粹是由于引用计数降到零?

有了这个设置,这是我的问题:

  • 以上是否有意义,或者我是否必须在其他地方寻找问题?在这个特定的用例中,重要数据长期存在只是一个不幸的事故吗?

  • 我能做些什么来避免这个问题.我已经看到了优化视图的一些潜力,但这似乎是一个范围有限的解决方案 - 尽管我不确定我的通用性是什么,或者; 例如,手动调用gc.collect()或gc.set_threshold()是否明智?

就垃圾收集器本身的工作方式而言:

  • 我是否正确理解对象总是移动到下一代,如果扫描查看它并确定它具有的引用不是循环的,但实际上可以跟踪到根对象.

  • 如果gc进行第1代扫描,并找到第2代中对象引用的对象,会发生什么?它是否遵循第2代内部的关系,还是在分析情况之前等待第2代扫描?

  • 当使用gc.DEBUG_STATS时,我主要关心的是"每一代中的对象"信息; 但是,我不断收到数百个"gc:0.0740s过去了.","gc:1258233035.9370s已经过去了." 消息; 它们非常不方便 - 打印出来需要相当长的时间,而且它们使得有趣的东西更难找到.有办法摆脱它们吗?

  • 我不认为有一种方法可以通过生成来执行gc.get_objects(),例如,只检索第2代中的对象?

Nic*_*ood 2

我认为你的分析看起来很合理。我不是这方面的专家gc,所以每当我遇到这样的问题时,我只是gc.collect()在适当的、非时间关键的地方添加一个调用,然后就忘记它了。

我建议您调用gc.collect()您的视图,看看它对您的响应时间和内存使用情况有什么影响。

另请注意这个问题,它表明设置DEBUG=True会消耗内存,就像它快要过了销售日期一样。