Python垃圾回收和GC模块

译文 精选
开发
Python内存管理是如何工作的,它的垃圾回收系统如何帮助优化Python程序中的内存,以及如何使用标准库和第三方模块来控制内存使用和垃圾回收。

译者 | 王德朕

策划 | 云昭

内存管理对于编程的重要性不言而喻。不管是技术面试,还是实际生产环境,始终都是开发者绕不开的一个门槛。

在Java领域,“JVM调优”成为了一个热议的话题。那么作为时不时占据编程排行榜的榜一大哥——Python,它是如何处理内存管理的呢?

本文就带大家详细了解Python垃圾回收系统的来龙去脉,以及如何避免它的陷阱。

Python为编程者提供了许多简单上手的特性,其中最大的便利之一就是(几乎)无障碍的内存管理。在Python中,不需要手动为Python中的对象和数据结构分配、跟踪和释放内存,Python运行时为你完成了所有这些工作,你可以专注于解决实际问题,而不是处理机器层面的细节。

尽管如此,对于经验不足的Python程序员来说,了解Python的垃圾收集和内存管理是如何工作的还是很有好处的。理解这些机制将有助于避免复杂项目出现性能问题,还可以使用Python的内置工具来监视程序的内存管理行为。

在这篇文章中,我们将看看Python内存管理是如何工作的,它的垃圾回收系统如何帮助优化Python程序中的内存,以及如何使用标准库和第三方模块来控制内存使用和垃圾回收。

1、Python如何管理内存

每个Python对象都有一个引用计数,也被称为refcount。refcount是对某个对象引用其它对象总数的统计。当你增加或删除对一个对象的引用时,这个数字会上升或下降,当一个对象的refcount变为零时,该对象就会被删除,其内存被释放。

什么是引用?允许通过名称或通过另一个对象中的访问器访问对象的任何东西。

这里有一个简单的例子:

x = "Hello there"

运行这段 Python代码,会发生两件事:

1.字符串 “Hello there” 作为一个 Python 对象被创建并存储在内存中;

2.在本地命名空间中创建变量 x ,并指向该对象,此时该对象的引用计数加1。

如果接下来的代码是“y=x”,那么引用计数将再次提高到2。

每当x和y超出作用域或者从它们的名称空间中删除时,字符串x和y的引用计数都会减少1。一旦x和y都超出作用域或被删除,字符串的refcount就变为0并被删除。

什么是作用域

作用域是名称空间的通用术语。默认情况下,在函数内定义的变量的作用域只是该函数,但在模块级别定义的名称的作用域是整个模块。有关更多详细信息,请参阅Python的文档。

现在,假设我们创建一个包含字符串的列表,如下所示:

x = ["Hello there", 2, False]

字符串一直保留在内存中,直到列表本身被删除或者包含字符串的元素从列表中被删除。这两个操作都会导致持有该字符串引用的对象消失。

现在考虑一下这个例子:

x="Hello there"
y=[x]

如果我们从y中删除第一个元素,或者完全删除列表y,那么字符串仍然在内存中,这是因为x包含对它的引用。

2、Python循环引用

大多数情况下,引用计数都是正常工作的,但有时你会遇到这样的情况:两个对象各自持有对方的一个引用,这就是所谓的循环引用。在这种情况下,对象的引用计数将永远不会达到零,它们也永远不会从内存中删除。

这里有一个人为的例子:

x = SomeClass()
y = SomeOtherClass()
x.item = y
y.item = x

由于x和y保持对彼此的引用,即使没有其它引用,它们也永远不会从系统中删除。

实际上,对于Python来说,为对象生成循环引用是相当常见的。一个例子是跟踪对象的异常,该对象包含对异常本身的引用。

在Python的早期版本中,具有循环引用的对象可能会随着时间积累,这对于长时间运行的应用程序来说是一个大问题。但Python后来引入了循环检测和垃圾回收系统,用于管理循环引用。

3、Python垃圾回收器(GC)

Python的垃圾回收器检测具有循环引用的对象。它通过跟踪作为“容器”的对象--例如列表、字典、自定义类实例,并确定其中有哪些对象不被引用。

一旦这些对象被挑选出来,垃圾回收器就会通过把它们的引用计数降低到0来删除它们。(有关这种方法的详细信息,请参阅Python开发人员指南。)

绝大多数Python对象没有循环引用,因此垃圾回收器不需要全天运行。相反,垃圾回收器使用一些方法来减少运行次数,并尽可能高效地运行。

当Python解释器启动时,它会跟踪已分配但未释放的对象数量,绝大多数Python对象的生命周期非常短,因此它们很快就会出现或消失。但是随着时间的推移,长期存在的对象会逐渐积累,当这种对象的数量超过一定数量时,垃圾回收器就会运行。(在Python 3.10中,默认允许的长生命周期对象数是700。)

每次垃圾回收器运行时,它都会将收集后的所有对象放在一起,并将它们放在一个称为“分代”的组中,在循环引用内,这些“第1代”对象被扫描的频率较低。任何在垃圾回收器中幸存下来的第1代对象最终都会迁移到第2代,在第2代中,它们很少被扫描。

同样,并不是所有的对象都会被垃圾回收器追踪到,例如像用户创建类这样的复杂对象总是被跟踪的,但是一个只保存简单对象,例如整数和字符串的字典不会被跟踪,因为在那个特定的字典中没有对象持有对其的引用,不持有对其它元素的引用的简单对象,如整数和字符串,永远不会被跟踪。

4、如何使用GC模块

一般来说,垃圾回收器不需要调整就可以运行良好,Python的开发团队选择了常见情况的默认值,如果你确实需要调整垃圾回收的工作方式,你可以使用Python的GC模块,GC模块为垃圾回收器的行为提供了编程接口,并可配置对哪些对象进行跟踪。

GC让你做的一件有用的事情是,当你确定不需要垃圾回收器的时候,可以关掉它。如果你有一个短期运行的脚本,堆积了大量的对象,你就不需要垃圾回收器。所有的东西都会在脚本结束时被清除掉。为此,你可以用gc.disable()命令禁用垃圾回收器,之后可以用gc.enable()重新启用它。

你还可以使用gc.collect() 手动运行垃圾回收,这方面的一个常见应用是管理程序中生成许多临时对象的部分,你可以在程序的这一部分禁用垃圾回收,然后在结束时手动运行回收并重新启用回收。

另一个有用的垃圾回收优化是gc.free(),当运行该代码后,垃圾回收器跟踪的所有内容都被“冻结”,或者被列为免于回收扫描,这样,未来的扫描可以跳过这些对象。如果你有一个导入库并在启动前设置大量内部状态的程序,那么可以在完成所有工作之后发出gc.free()。这样可以防止垃圾回收器搜寻那些无论如何都不可能被移除的东西。(如果希望将冻结的对象再次执行垃圾回收,请使用gc.unfree()。)

5、使用GC调试垃圾回收

还可以使用GC调试垃圾回收行为,如果内存中堆积的对象数量过多,而且没有被垃圾回收,那么可以使用GC的检查工具来确定哪些对象保存着对这些对象的引用。

如果想知道哪些对象保存着对给定对象的引用,可以使用gc.get_reference (obj)来列出它们,还可以使用gc.get_reference(obj)查找给定对象引用的任何。

如果不确定给定对象是否是垃圾回收的候选对象,gc.is_trace (obj)会告诉垃圾回收器是否跟踪该对象,如前所述,请记住垃圾回收器不会跟踪“原子”对象(如整数)或仅包含原子对象的元素。

如果希望亲自查看正在收集的对象,可以使用gc.set_debug(gc.DEBUG _ LEAK | gc.DEBUG_STATS)设置垃圾收集器的调试标志。这将有关垃圾收集的信息写入stderr,它将所有作为垃圾收集的对象保存在只读列表gc.garbage中。

6、避免Python内存管理中的陷阱

如前所述,对象可能堆积在内存中,如果在某个地方仍然有对它们的引用,则不会被回收。这并不是Python的垃圾回收本身的问题,而是因为垃圾回收器无法判断你是否意外地保留了对某些内容的引用。

让我们以一些防止对象出现永不回收的提示来结束本文。

注意对象作用域

如果你把对象1指定为对象2的一个属性(比如一个类),对象2需要在对象1之前退出作用范围。

obj1 = MyClass()
obj2.prop = obj1


更重要的是,如果这是以其它操作的副作用形式方式发生的,例如把对象2作为参数传递给对象1的构造函数,你可能没有意识到对象1有一个引用。

obj1 = MyClass(obj2)

另一个例子,如果你把一个对象推到一个模块级的列表中,然后忘记了这个列表,这个对象将一直存在,直到从列表中删除,或者直到列表本身不再有任何引用。但是如果这个列表是一个模块级的对象,它很可能会一直存在,直到程序终止。

简而言之,要意识到对象可能被另一个不明显的对象引用。

使用weakref避免循环引用

Python的weakref模块让你创建对其它对象的弱引用,弱引用不会增加一个对象的引用数,所以一个只有弱引用的对象是垃圾回收的候选对象。

weakref的一个常见用途是对象的缓存,如果不希望被引用的对象仅仅因为有一个缓存条目而被保留下来,可以对缓存条目使用弱引用。

手动中断循环引用

最后,如果你知道一个给定的对象持有对另一个对象的引用,你可以手动中断对该对象的引用,如果你有instance_of_class.ref = other_object,当你准备移除instance_of_class时,你可以设置instance_of_class.ref = None。

原文链接:

https://www.infoworld.com/article/3671673/python-garbage-collection-and-the-gc-module.html

译者介绍

王德朕,51CTO社区编辑,10年互联网产研经验,6年IT教培行业经验。

责任编辑:薛彦泽 来源: 51CTO
相关推荐

2022-01-20 10:34:49

JVM垃圾回收算法

2022-03-21 11:33:11

JVM垃圾回收器垃圾回收算法

2014-06-19 10:48:18

RubyPython

2023-12-07 12:21:04

GCJVM垃圾

2021-11-05 15:23:20

JVM回收算法

2022-01-25 09:15:39

V8垃圾回收算法

2021-09-26 09:23:01

GC算法垃圾

2017-08-17 15:40:08

大数据Python垃圾回收机制

2017-08-04 10:53:30

回收算法JVM垃圾回收器

2021-01-04 10:08:07

垃圾回收Java虚拟机

2012-12-18 13:57:42

.NetC#

2021-05-27 21:47:12

Python垃圾回收

2017-06-12 17:38:32

Python垃圾回收引用

2023-05-12 07:38:46

Python基准测试性能分析

2020-03-13 08:00:00

.NET对象清理垃圾回收

2021-06-09 06:24:03

java垃圾回收机Java语言

2019-12-02 16:23:03

Python编程语言“垃圾”回收

2010-12-13 11:14:04

Java垃圾回收算法

2023-08-08 10:29:55

JVM优化垃圾回收

2010-09-26 11:22:22

JVM垃圾回收JVM
点赞
收藏

51CTO技术栈公众号