由于您的代码,是否可能在Python中存在实际的内存泄漏?

时间:2010-01-07 00:27:34

标签: python memory-leaks

我没有代码示例,但我很好奇是否有可能编写导致内存泄漏的Python代码。

6 个答案:

答案 0 :(得分:103)

有可能,是的。

这取决于您所谈论的内存泄漏类型。在纯python代码中,不可能“忘记释放”内存,例如在C中,但是可以将引用挂在某处。一些这样的例子:

一个未处理的回溯对象,即使该函数不再运行,也会使整个堆栈帧保持活动状态

while game.running():
    try:
        key_press = handle_input()
    except SomeException:
        etype, evalue, tb = sys.exc_info()
        # Do something with tb like inspecting or printing the traceback

在游戏循环的这个愚蠢的例子中,我们可以将“tb”分配给本地。我们有良好的意图,但是这个tb包含有关我们的handle_input中发生的任何事情的堆栈的框架信息,一直到这个调用。假设你的游戏继续,即使你下次调用handle_input,这个'tb'也会保持活跃,也许永远。 docs for exc_info现在谈论这个潜在的循环引用问题,如果您不是绝对需要,建议不要分配tb。如果你需要追溯,请考虑例如traceback.format_exc

将值存储在类或全局范围而不是实例范围中,而不是实现它。

这个可以以阴险的方式发生,但是当您在类范围中定义可变类型时,通常会发生这种情况。

class Money(object):
    name = ''
    symbols = []   # This is the dangerous line here

    def set_name(self, name):
        self.name = name

    def add_symbol(self, symbol):
        self.symbols.append(symbol)

在上面的例子中,说你做了

m = Money()
m.set_name('Dollar')
m.add_symbol('$')

你可能会很快找到这个特定的bug,但是在这种情况下你在类范围内放了一个可变值,即使你在实例范围内正确访问它,它实际上是“掉头”到了类对象__dict__

这在某些上下文中使用,例如保存对象可能会导致导致应用程序堆永远增长的事情,并且会导致问题,例如生产Web应用程序偶尔不会重新启动其进程。

类中的循环引用也具有__del__方法。

具有讽刺意味的是,__del__的存在使得循环垃圾收集器无法清理实例。假设你有一些你想要用于最终目的的析构函数的东西:

class ClientConnection(...):
    def __del__(self):
        if self.socket is not None:
            self.socket.close()
            self.socket = None

现在这可以自行运行,并且可能会让您相信它是操作系统资源的良好管理者,以确保套接字被“处置”。

但是,如果ClientConnection保留引用说明User并且用户保留对连接的引用,那么您可能会想要在清理时让我们让用户取消引用该连接。但是This is actually the flaw:循环GC不知道正确的操作顺序,也无法清理它。

解决这个问题的方法是确保你通过调用某种类型的close来断开事件,但是将该方法命名为__del__以外的其他方法。

实施不当的C扩展,或者没有正确使用C库。

在Python中,您相信垃圾收集器可以丢弃您不使用的内容。但是,如果使用包装C库的C扩展,则大多数时候您负责确保明确关闭或取消分配资源。大多数情况下都记录了这一点,但是一个习惯于不必进行这种显式解除分配的python程序员可能会丢弃句柄(比如从函数或其他任何东西返回)到该库而不知道资源被保存。

包含闭包的范围,其中包含的内容远远超出您的预期

class User:
    def set_profile(self, profile):
        def on_completed(result):
            if result.success:
                self.profile = profile

        self._db.execute(
            change={'profile': profile},
            on_complete=on_completed
        )

在这个人为设想的例子中,我们似乎正在使用某种“异步”调用,当数据库调用完成后,它会在on_completed调用我们(实现可能是承诺,最终是相同的结果)。

您可能没有意识到,on_completed闭包绑定了对self的引用,以便执行self.profile赋值。现在,也许DB客户端跟踪活动查询和指向闭包的指针,以便在它们完成时调用(因为它是异步的)并且说它因任何原因而崩溃。如果数据库客户端没有正确清理回调等,在这种情况下,数据库客户端现在有一个对on_completed的引用,它引用了一个保留_db的用户 - 你现在已经创建了一个循环引用,永远不会被收集。

(即使没有循环引用,闭包绑定本地甚至实例的事实有时可能会导致您认为收集的值长时间存在,其中可能包括套接字,客户端,大缓冲区和整个事物树)

默认参数是可变类型

def foo(a=[]):
    a.append(time.time())
    return a

这是一个人为的例子,但可以让人们相信a的默认值是一个空列表意味着附加到它,当它实际上是对相同的引用列表。如果不知道你那样做,这又可能导致无限制的增长。

答案 1 :(得分:15)

内存泄漏的经典定义是曾经使用过一次的内存,现在却没有,但还没有被回收。使用纯Python代码几乎不可能。但正如Antoine所指出的那样,即使您不需要保留所有数据,也可以通过允许数据结构无限制地增长来轻松地消耗所有内存。

当然,使用C扩展名,您将回到非托管区域,一切皆有可能。

答案 2 :(得分:11)

当然可以。内存泄漏的典型示例是,如果您构建一个永远不会手动刷新且没有自动驱逐策略的缓存。

答案 3 :(得分:0)

在分配对象超出范围后因为忘记解除分配而无法分配对象; Python将自动释放超出范围的对象(Garbage Collection)。但就@Antione所说的那样,是的。

答案 4 :(得分:-1)

由于许多模块都是用C编写的,因此,可能会发生内存泄漏。 假设您使用的是gui绘图环境(例如wxpython),则可以创建内存缓冲区,但是如果忘记释放它。你将有内存泄漏... 在这种情况下,wx api的C ++函数被包装为python。

更大的错误用法,想象一下您在python中重载了这些wx窗口小部件方法...保证内存泄漏。

答案 5 :(得分:-1)

我创建了一个具有沉重属性的对象,以炫耀进程内存使用情况。

然后我创建一个字典,该字典会多次引用自身。

然后我删除对象,并要求GC收集垃圾。它不收集任何东西。

然后我检查进程的RAM占用空间-相同。

在这里,内存泄漏!

α python
Python 2.7.15 (default, Oct  2 2018, 11:47:18)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import gc
>>> class B(object):
...     b = list(range(1 * 10 ** 8))
...
>>>
[1]+  Stopped                 python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164   0.0 19.0  7562952 3188184 s010  T     2:08pm   0:03.78 /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
~/Sources/plan9port [git branch:master]
α fg
python

>>> b = B()
>>> for i in range(1000):
...     b.a = {'b': b}
...
>>>
[1]+  Stopped                 python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164   0.0 19.0  7579336 3188264 s010  T     2:08pm   0:03.79 /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
~/Sources/plan9port [git branch:master]
α fg
python


>>> b.a['b'].a
{'b': <__main__.B object at 0x109204950>}
>>> del(b)
>>> gc.collect()
0
>>>
[1]+  Stopped                 python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164   0.0 19.0  7579336 3188268 s010  T     2:08pm   0:05.13 /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python