我有一个Django应用程序,展现出一些奇怪的垃圾回收行为。特别是有一个视图,每次调用时都会显著增加虚拟内存大小 - 直到达到一定限制,此时使用量再次下降。问题在于,直到达到该点需要相当长的时间,实际上运行我的应用程序的虚拟机没有足够的内存供所有FCGI进程使用那么多内存。
我花了最近两天的时间研究这个问题并了解Python垃圾回收,我现在认为我基本上理解发生了什么。当使用
因此,大量的对象被分配,但最初被移动到第一代,并且在同一请求中清除第一代时,它们被移动到第二代。 如果之后我手动执行gc.collect(2),它们将被删除。 此外,正如我提到的那样,当下一个自动gen 2扫描发生时,它们也会被删除,在这种情况下,每隔大约10个请求(此时应用程序需要大约150MB)。
好的,最初我认为在处理一个请求时可能存在某些循环引用,阻止任何这些对象在处理该请求时被收集。 然而,我花了几个小时使用pympler.muppy和objgraph查找其中一个,包括请求处理内部的调试,但似乎没有。 相反,似乎在请求期间创建的大约14,000个对象都在参考链中与某个请求全局对象相关联,即一旦请求消失,它们就可以被释放。
无论如何,这是我的解释尝试。 但是,如果确实如此,并且确实没有循环依赖关系,那么不应该通过引用计数降至零而不涉及垃圾回收器的情况下释放所有对象树吗?
有了这个设置,以下是我的问题:
我花了最近两天的时间研究这个问题并了解Python垃圾回收,我现在认为我基本上理解发生了什么。当使用
gc.set_debug(gc.DEBUG_STATS)
对于单个请求,我看到以下输出:
>>> 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.
因此,大量的对象被分配,但最初被移动到第一代,并且在同一请求中清除第一代时,它们被移动到第二代。 如果之后我手动执行gc.collect(2),它们将被删除。 此外,正如我提到的那样,当下一个自动gen 2扫描发生时,它们也会被删除,在这种情况下,每隔大约10个请求(此时应用程序需要大约150MB)。
好的,最初我认为在处理一个请求时可能存在某些循环引用,阻止任何这些对象在处理该请求时被收集。 然而,我花了几个小时使用pympler.muppy和objgraph查找其中一个,包括请求处理内部的调试,但似乎没有。 相反,似乎在请求期间创建的大约14,000个对象都在参考链中与某个请求全局对象相关联,即一旦请求消失,它们就可以被释放。
无论如何,这是我的解释尝试。 但是,如果确实如此,并且确实没有循环依赖关系,那么不应该通过引用计数降至零而不涉及垃圾回收器的情况下释放所有对象树吗?
有了这个设置,以下是我的问题:
上述内容是否有意义,还是我必须在其他地方寻找问题? 在这种特定用例中保留重要数据是否只是不幸的事故?
有什么可以避免此问题的方法吗? 我已经看到了优化视图的潜力,但似乎这只是一个范围有限的解决方案-尽管我不确定通用解决方案是什么;例如,手动调用gc.collect()或gc.set_threshold()有多可取?
就垃圾回收器本身的工作方式而言:
我理解得对吗,如果一次垃圾回收遍历一个对象并确定它的引用不是循环引用,而实际上可以追溯到一个根对象,那么该对象总是会被移动到下一代。
如果垃圾回收进行了一次例如一代的扫描,并且发现一个对象被二代中的对象引用。那么它是否会在二代内跟进这个关系,或者等待二代的扫描再分析情况?
当使用gc.DEBUG_STATS时,我主要关心每个代中的“对象数量”信息。然而,我经常会看到“gc: 0.0740秒耗时。”、“gc: 1258233035.9370秒耗时。”等数百条消息。它们非常不方便——需要相当长的时间才能打印出来,而且让有趣的事情更难找到。有没有办法摆脱它们?
我不知道是否有一种方式可以按代数执行gc.get_objects(),例如只检索来自第二代的对象?