译者|王德真策划|YunZhao内存管理对于编程来说是不言而喻的。无论是技术面试还是实际生产环境,它始终是开发者绕不开的门槛。在Java界,“JVM调优”已经成为热门话题。那么,作为时不时占据编程排行榜的老大哥——Python,它是如何处理内存管理的呢?本文将带您了解Python垃圾收集系统的来龙去脉以及如何避免其陷阱。Python为程序员提供了许多易于使用的特性,其中最大的便利之一是(几乎)无忧的内存管理。在Python中,Python运行时为您完成所有这些工作,而不是手动为Python中的对象和数据结构分配、跟踪和释放内存,您可以专注于解决实际问题,而不是处理机器级别的细节。尽管如此,对于经验不足的Python程序员来说,了解Python的垃圾收集和内存管理的工作原理还是很有帮助的。了解这些机制将帮助您避免在复杂项目中出现性能问题,并且您可以使用Python的内置工具来监控程序的内存管理行为。在本文中,我们将了解Python内存管理的工作原理,其垃圾收集系统如何帮助优化Python程序中的内存,以及如何使用标准库和第三方模块来控制内存使用和垃圾收集。一、Python如何管理内存每个Python对象都有一个引用计数,也称为refcount。Refcount是一个对象对其他对象的引用总数的计数。当您添加或删除对对象的引用时,此数字会增加或减少,并且当对象的引用计数变为零时,该对象将被删除并释放其内存。什么是引用?允许通过名称或通过另一个对象中的访问器访问对象的任何内容。这是一个简单的例子:x="Hellothere"运行这段Python代码,会发生两件事:1.字符串“Hellothere”被创建并作为Python对象存储在内存中;2、本地命名在空间中创建变量x并指向对象,此时对象的引用计数加1。如果下面的代码是“y=x”,那么引用计数会再次增加到2。每次x和y超出范围或从其名称空间中删除时,字符串x和y的引用计数都会减1。一旦x和y都超出范围或被删除,字符串的引用计数将变为0并被删除。什么是作用域作用域是命名空间的总称。默认情况下,函数内定义的变量仅对该函数具有作用域,但在模块级别定义的名称具有整个模块的作用域。有关更多详细信息,请参阅Python的文档。现在,假设我们创建一个包含如下字符串的列表:x=["Hellothere",2,False]字符串保留在内存中,直到列表本身被删除或包含该字符串的元素从列表中删除。这两个操作都会导致持有字符串引用的对象消失。现在考虑这个例子:x="Hellothere"y=[x]如果我们从y中删除第一个元素,或者整个列表y,字符串仍然在内存中,因为x包含对它的引用quote。2.Python循环引用大多数情况下,引用计数是正常工作的,但有时你会遇到这样的情况:两个对象各自持有对另一个对象的引用,这就是所谓的循环引用。在这种情况下,对象的引用计数永远不会达到零,它们也永远不会从内存中删除。这是一个人为的例子:x=SomeClass()y=SomeOtherClass()x.item=yy.item=x因为x和y保持对彼此的引用,即使没有其他引用,它们也永远不会从系统中删除。事实上,Python为对象生成循环引用是相当常见的。一个例子是异常跟踪对象,它包含对异常本身的引用。在早期版本的Python中,具有循环引用的对象会随着时间的推移而累积,这对于长时间运行的应用程序来说是个大问题。但是Python后来引入了循环检测和垃圾收集系统来管理循环引用。3.Python垃圾收集器(GC)Python的垃圾收集器检测具有循环引用的对象。它通过跟踪作为“容器”的对象(例如列表、字典、自定义类实例)并确定其中哪些未被引用来工作。一旦这些对象被挑选出来,垃圾收集器通过将它们的引用计数减少到0来删除它们。(有关此方法的详细信息,请参阅Python开发人员指南。)绝大多数Python对象没有循环引用,因此垃圾收集器没有不需要整天跑。相反,垃圾收集器使用方法来减少运行次数并尽可能高效地运行。当Python解释器启动时,它会跟踪已分配但未释放的对象数。绝大多数Python对象的生命周期都很短,因此它们出现或消失的速度很快。但是随着时间的推移,长寿命的对象会累积,当此类对象的数量超过一定数量时,垃圾收集器就会运行。(在Python3.10中,默认允许的长寿命对象的数量是700。)每次垃圾收集器运行时,它会将它收集的所有对象放在一起,并将它们放在一个称为“世代”的集合中,这些“第一代”对象在循环引用中的扫描频率较低。任何在垃圾收集器中存活下来的第1代对象最终都会迁移到第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垃圾收集本身的问题,而是因为垃圾收集器无法判断您是否不小心保留了对某物的引用。让我们以一些防止对象永远不会被收集的技巧来结束这篇文章。关于对象范围的注意事项如果将object1指定为object2的属性(例如类),则object2需要先于object1超出范围。obj1=MyClass()obj2.prop=obj1更重要的是,如果这是作为其他操作的副作用发生的,例如将对象2作为参数传递给对象1的构造函数,您可能没有意识到对象1具有引用。obj1=MyClass(obj2)另一个例子,如果你将一个对象推入一个模块级列表,然后忘记这个列表,这个对象将继续存在,直到从列表中删除,或者直到列表本身没有更多的引用。但如果列表是模块级对象,它可能会一直存在到程序终止。简而言之,请注意一个对象可能被另一个不明显的对象引用。使用weakref避免循环引用Python的weakref模块允许您创建对其他对象的弱引用。弱引用不会增加对象的引用计数,因此只有弱引用的对象是垃圾回收的候选对象。weakrefs的一个常见用途是缓存对象。如果不想因为有缓存项就保留引用的对象,可以使用弱引用来缓存项。手动打破循环引用最后,如果你知道一个给定的对象持有对另一个对象的引用,你可以手动打破对该对象的引用,如果你有instance_of_class.ref=other_object,当你准备删除instance_of_class时,你可以设置instance_of_class.ref=无。原文链接:https://www.infoworld.com/article/3671673/python-garbage-collection-and-the-gc-module.html译者简介王德真,社区编辑,10年互联网产研经验,6年IT教育培训行业经验。
