麻烦了解python的gc.garbage(用于跟踪内存泄漏)

ger*_*jan 7 python garbage-collection memory-leaks

我的一个python应用程序似乎泄漏内存,从稳定增加的内存使用量来判断.我的假设是某个地方的循环引用,尽管尽最大努力避免这种情况.为了隔离问题,我正在研究手动检查无法访问的项目的方法,这是一种纯粹针对调试的工具.

gc模块似乎能够进行必要的跟踪,我尝试了以下代码,旨在编译自上次调用以来形成的不可缓存项目列表.第一个调用仅设置基本检查点,不会识别无法访问的项目.

def unreachable():
  # first time setup
  import gc
  gc.set_threshold( 0 ) # only manual sweeps
  gc.set_debug( gc.DEBUG_SAVEALL ) # keep unreachable items as garbage
  gc.enable() # start gc if not yet running (is this necessary?)
  # operation
  if gc.collect() == 0:
    return 'no unreachable items'
  s = 'unreachable items:\n ' \
    + '\n '.join( '[%d] %s' % item for item in enumerate( gc.garbage ) )
  _deep_purge_list( gc.garbage ) # remove unreachable items
  return s # return unreachable items as text
Run Code Online (Sandbox Code Playgroud)

这里,_deep_purge_list旨在打破循环并手动删除对象.以下实现处理一些常见情况但不接近防水.我的第一个问题与此有关,请注意.

def _deep_purge_list( garbage ):
  for item in garbage:
    if isinstance( item, dict ):
      item.clear()
    if isinstance( item, list ):
      del item[:]
    try:
      item.__dict__.clear()
    except:
      pass
  del garbage[:]
Run Code Online (Sandbox Code Playgroud)

基于非常有限的测试,设置似乎正常运行.以下循环引用正确报告一次:

class A( object ):
  def __init__( self ):
    self.ref = self

print unreachable()
# no unreachable items

A()

print unreachable()
# unreachable items:
#  [0] <__main__.A object at 0xb74579ac>
#  [1] {'ref': <__main__.A object at 0xb74579ac>}

print unreachable()
# no unreachable items
Run Code Online (Sandbox Code Playgroud)

然而,随着以下事情发生奇怪:

print unreachable()
# no unreachable items

import numpy

print unreachable()
# unreachable items:
#  [0] (<type '_ctypes.Array'>,)
#  [1] {'__module__': 'numpy.ctypeslib', '__dict__': <attribute '__dict__' of 'c_long_Array_1' objects>, '__weakref__': <attribute '__weakref__' of 'c_long_Array_1' objects>, '_length_': 1, '_type_': <class 'ctypes.c_long'>, '__doc__': None}
#  [2] <class 'numpy.ctypeslib.c_long_Array_1'>
#  [3] <attribute '__dict__' of 'c_long_Array_1' objects>
#  [4] <attribute '__weakref__' of 'c_long_Array_1' objects>
#  [5] (<class 'numpy.ctypeslib.c_long_Array_1'>, <type '_ctypes.Array'>, <type '_ctypes._CData'>, <type 'object'>)

print unreachable()
# unreachable items:
#  [0] (<type '_ctypes.Array'>,)
#  [1] {}
#  [2] <class 'c_long_Array_1'>
#  [3] (<class 'c_long_Array_1'>, <type '_ctypes.Array'>, <type '_ctypes._CData'>, <type 'object'>)
Run Code Online (Sandbox Code Playgroud)

重复调用会继续返回最后的结果.导入后第一次调用无法访问时,不会发生此问题.但是,在这一点上,我没有理由相信这个问题是特定的; 我的猜测是它暴露了我的方法中的一个缺陷.

我的问题:

  1. 有没有更好的方法来删除gc.garbage中的项目?理想情况下,有没有办法让gc删除它们,就像它应该(没有DEBUG_SAVEALL那样)?
  2. 任何人都可以用numpy导入解释问题,和/或建议修复方法吗?

事后:

看来下面的代码表现接近预期:

def unreachable():
  import gc
  gc.set_threshold( 0 )
  gc.set_debug( gc.DEBUG_LEAK )
  gc.enable()
  print 'collecting {{{'
  gc.collect()
  print '}}} done'
Run Code Online (Sandbox Code Playgroud)

但是,对于调试,我更喜欢由gc提供的类型/ id上的丰富字符串表示.此外,我想了解我以前的方法中的缺陷,并了解一下gc模块.

感谢你的帮助,

Gertjan

更新06/05:

我遇到了第一个实现没有报告任何无法访问的项目的情况,除非在它之前调用了locals()(丢弃了返回值).不理解这可能会如何影响gc的对象跟踪,这让我更加困惑.我不确定构建一个演示这个问题的小例子是多么容易,但如果需要它,我可以试一试.

小智 0

上次我有这样的需求时,我最终使用了该objgraph模块,效果很好。gc它提供的信息比您直接从模块轻松获取的信息要准确得多。不幸的是,我手头没有任何代码来说明其用法。

它失败的一个地方是由任何调用的 C 代码库分配的内存。例如,如果一个项目使用 PIL,则很容易由于未正确释放由 C 数据支持的 python 对象而泄漏内存。如何正确关闭此类对象取决于 C 支持的模块。