📜  Python中的垃圾回收

📅  最后修改于: 2020-04-08 01:24:19             🧑  作者: Mango

Python的内存分配和释放方法是自动的。用户不必像在C或C++这样的语言中使用动态内存分配那样,就可以预先分配或取消分配内存。
Python使用两种策略进行内存分配:

  • 参考计数
  • 垃圾收集

在Python 2.0版之前,Python解释器仅将引用计数用于内存管理。引用计数是通过计算一个对象被系统中其他对象引用的次数来进行的。删除对对象的引用后,对象的引用计数将减少。当引用计数变为零时,将释放对象:

# 9是一个对象
b = 9
# 对象9的引用计数变为0。
b = 4

文字值9是一个对象。对象9的引用计数在第1行中递增为1。在第2行中,其引用计数在取消引用时变为零。因此,垃圾收集器会释放对象。

当无法达到对象的引用计数时,将创建一个引用循环。涉及列表,元组,实例,类,字典和函数的参考循环是常见的。创建参考循环的最简单方法是创建一个引用自身的对象,如下例所示:

def create_cycle():
    # 创建一个列表x
    x = [ ]
    # 由于x包含对self的引用,因此在此处创建了一个引用循环。
    x.append(x)
create_cycle()

因为create_cycle()创建了一个引用自身的对象x,所以函数返回时不会自动释放该对象x。这将导致x使用的内存被保留,直到调用Python垃圾收集器为止。

使对象符合垃圾收集条件的方法

x = []
x.append(l)
x.append(2)
# 从内存中删除列表或将对象x分配给None(Null)
del x
# x = None

现在,所创建列表的引用计数为2。但是,由于无法从Python内部访问它并且无法再次使用,因此将其视为垃圾。在当前版本的Python中,永远不会释放此列表。

自动回收垃圾
因为引用周期需要计算工作才能发现,所以垃圾回收必须是计划的活动。Python根据对象分配和对象释放的阈值调度垃圾回收。当分配数减去取消分配数大于阈值数时,将运行垃圾收集器。可以通过导入gc模块并询问垃圾收集阈值来检查新对象的阈值:

# 导入 gc
import gc
# 以元组的形式获取当前集合的阈值
print("垃圾收集阈值:",
                    gc.get_threshold())

输出:

垃圾收集阈值: (700, 10, 10)

此处,上述系统的默认阈值为700。这意味着,当分配数量与释放数量相比大于700时,将运行自动垃圾收集器。因此,代码中释放大量内存块的任何部分都是运行手动垃圾回收的理想选择。

手动垃圾收集

在程序执行期间手动调用垃圾收集器可能是一个很好的主意,它说明如何处理参考周期消耗的内存。
垃圾回收可以通过以下方式手动调用:

# 导入gc块
import gc
# 返回已收集并释放的对象数
collected = gc.collect()
# 将垃圾收集器打印为0对象
print("垃圾收集器: 已收集",
          "%d 对象." % collected)

如果创建的周期很少,那么手动收集的工作方式如下:

import gc
i = 0
# 创建一个循环,并在每次迭代x上将其分配为1的字典
def create_cycle():
    x = { }
    x[i+1] = x
    print x
# 只要运行完整的集合,就会清除列表
collected = gc.collect() # or gc.collect(2)
print "垃圾收集器: 已收集 %d 对象." % (collected)
print "创建循环..."
for i in range(10):
    create_cycle()
collected = gc.collect()
print "垃圾收集器: 已收集 %d 对象." % (collected)

输出:

垃圾收集器: 已收集 0 对象.
创建循环...
{1: {...}}
{2: {...}}
{3: {...}}
{4: {...}}
{5: {...}}
{6: {...}}
{7: {...}}
{8: {...}}
{9: {...}}
{10: {...}}
垃圾收集器: 已收集 10 对象.

有两种执行手动垃圾收集的方法:基于时间的垃圾收集和基于事件的垃圾收集。
基于时间的垃圾收集很简单:在固定的时间间隔后调用垃圾收集器。
基于事件的垃圾收集会在事件发生时调用垃圾收集器。例如,当用户退出应用程序时或应用程序进入空闲状态时。