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

oro*_*aki 50 python memory-leaks

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

Cra*_*ast 112

有可能,是的.

这取决于你所谈论的内存泄漏类型.在纯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
Run Code Online (Sandbox Code Playgroud)

在游戏循环的这个愚蠢的例子中,我们将'tb'分配给了本地.我们有良好的意图,但是这个tb包含有关我们的handle_input中发生的任何事情的堆栈的框架信息,一直到这个调用.假设你的游戏继续,即使在你下一次调用handle_input时,这个'tb'仍然存在,也许永远.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)
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,假设你做到了

m = Money()
m.set_name('Dollar')
m.add_symbol('$')
Run Code Online (Sandbox Code Playgroud)

您可能很快就会发现这个特定的错误,但是在这种情况下,您在类范围内放置了一个可变值,即使您在实例范围内正确访问它,它实际上是"落到" 类对象__dict__.

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

类中的循环引用也有一个__del__方法.

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

class ClientConnection(...):
    def __del__(self):
        if self.socket is not None:
            self.socket.close()
            self.socket = None
Run Code Online (Sandbox Code Playgroud)

现在这个工作正常,你可能会认为它是操作系统资源的良好管理者,以确保套接字被"处置".

但是,如果ClientConnection保留了对say的引用,User并且User保留了对该连接的引用,那么您可能会想要在清理时让我们让用户取消引用该连接.这实际上是一个缺陷:循环GC不知道正确的操作顺序,也无法清理它.

解决这个问题的方法是确保你通过调用某种关闭方式来清理事件,但是将该方法命名为其他方法__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
        )
Run Code Online (Sandbox Code Playgroud)

在这个人为的例子中,我们似乎正在使用某种"异步"调用,它会on_completed在DB调用完成时调用我们(实现可能是承诺,最终会得到相同的结果).

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

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

默认参数是可变类型

def foo(a=[]):
    a.append(time.time())
    return a
Run Code Online (Sandbox Code Playgroud)

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

  • 你能否详细说明或提供一些关于你的第一个项目符号的文档"一个未处理的回溯对象,它保持整个堆栈框架活着,即使该函数不再运行"?我实际上有一个回溯泄漏,但没有看到它为什么在任何地方被引用(/sf/ask/3127717701/​​ould-prevent-a-traceback-from-being-garbage-collected) (2认同)

Ned*_*der 15

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

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


Ant*_* P. 11

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

  • 从技术上讲,这不是内存泄漏,因为应用程序仍然能够释放内存,尽管它选择不这样做. (6认同)
  • 那么我只是认为我们以不同的方式定义"内存泄漏".对我来说,泄漏是泄漏,无论是否可以修复. (3认同)
  • 我认为内存泄漏是在完成其目的后不释放内存的任何事情。然而,我不会将管理不当的缓存称为内存泄漏,只是因为它并没有真正完成。这只是浪费。 (3认同)
  • 根据您的定义,长时间运行的进程在“完成”之前不会发生内存泄漏,这对我来说听起来有点不合适。 (2认同)