在一个 Python 代码中,它迭代了 30 个涉及内存和 CPU 密集型数值计算的问题序列,我观察到 Python 进程的内存消耗在 30 次迭代中的每一次迭代开始时增长了约 800MB,最后在第 8 次迭代中引发 MemoryError
(此时系统的内存实际上已耗尽)。但是,如果我 import gc
并让 gc.collect()
在每次迭代后运行,那么内存消耗将保持在 ~2.5GB 不变并且 Python 代码在解决后很好地终止所有30个问题。代码只使用了连续2个问题的数据,没有引用循环(否则手动垃圾回收也无法降低内存消耗)。
问题
如果 Python 在引发 MemoryError
之前尝试运行垃圾收集器,则此行为会引发问题。在我看来,这是一件非常明智的事情,但也许有理由反对这样做?
此处进行了与上述类似的观察:https://stackoverflow.com/a/4319539/1219479
最佳答案
实际上,存在 引用循环,这是手动gc.collect()
调用能够完全回收内存的唯一原因。
在 Python 中(我在这里假设是 CPython),垃圾收集器的唯一目的是打破引用循环。如果不存在,对象将被销毁,并在最后一次引用丢失的那一刻回收它们的内存。
至于何时运行垃圾收集器,完整的文档在这里:http://docs.python.org/2/library/gc.html
它的 TLDR 是 Python 维护对象分配和释放的内部计数器。每当 (allocations - deallocations)
达到 700(阈值 0)时,将运行垃圾收集并重置两个计数器。
每次收集发生时(自动或使用 gc.collect()
手动运行),都会收集第 0 代(所有尚未通过收集的对象)(即,遍历没有可访问引用的对象,寻找引用循环——如果找到任何循环,则循环被打破,可能导致对象被销毁,因为没有引用留下)。该收集之后保留的所有对象都移至第 1 代。
每 10 次收集(阈值 1),第 1 代也被收集,所有第 1 代中存活的对象 that 移动到第 2 代。第 1 代的每 10 个收集(即每100 个集合——阈值 2),第 2 代也被收集。留在第 2 代中的幸存对象 - 没有第 3 代。
用户可以通过调用 gc.set_threshold(threshold0, threshold1, threshold2)
设置这 3 个阈值。
这对您的程序意味着什么:
- GC 不是 CPython 用来回收内存的机制(refcounting 是)。 GC 会破坏“死”对象中的引用循环,这可能会导致其中一些对象被销毁。
- 不,不能保证 GC 会在引发
MemoryError
之前运行。 - 你有引用周期。尝试摆脱它们。
关于Python:垃圾收集器是否在引发 MemoryError 之前运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22440421/