跳至主要内容

Python 垃圾回收:关键概念与机制

了解 Python 如何通过引用计数与分代垃圾回收自动管理内存,并学习如何使用 gc 模块手动控制垃圾回收。
· 9分钟 阅读

使用 AI 探索

内存管理是编程中重要却常被忽视的一环。若处理不当,可能导致应用变慢、意外崩溃,甚至出现内存泄漏。所幸,Python 通过称为垃圾回收的流程来处理这一问题,这是一个内置的、自动管理内存的系统。

那么,Python 的垃圾回收如何工作,您为什么需要关心?理解这一过程是编写高效、少缺陷、性能良好的代码的关键,即使您的项目日益复杂也能保持稳定表现。

阅读完本文,您将了解 Python 中垃圾回收的基础知识及其重要性,明白 Python 如何通过引用计数与分代垃圾回收来管理内存,并学到一些避免内存泄漏的技巧。最后,亦建议您报名我们的 Writing Efficient Python Code 课程,学习如何在您的环境中合理分配资源并权衡开销,垃圾回收只是其中一个主题。

什么是 Python 中的垃圾回收?

垃圾回收是 Python 自动管理内存的方式,它通过释放不再使用的内存,确保您的应用顺畅运行。简单来说,这就像有个看不见的清洁员在您的代码之后打扫,将不再需要的对象清理掉。

Python garbage collection

“Python collecting garbage.” 图片来源:Dall-E 2

为什么 Python 需要垃圾回收?

在任何应用中,对象被创建来存储数据、执行计算或管理任务。然而,一旦这些对象完成了使命,它们仍会占用内存,直到被显式移除。如果这些未使用的对象不断累积,会导致应用使用超过必要的内存,从而性能下降,甚至发生崩溃。

垃圾回收可避免此类情况。它会自动检测对象何时不再需要(代码的任何部分都不再引用它),并安全地将其从内存中移除。此过程有助于:

  • 防止内存泄漏: 通过自动清理未使用的对象,垃圾回收降低了内存泄漏的风险,即本应释放的内存未被释放。
  • 优化性能: 通过释放内存,垃圾回收有助于维持应用性能,尤其适用于长时间运行的程序或需要处理大量数据的程序。
  • 简化开发: 由于 Python 自动处理内存管理,开发者可以专注于编写代码,而非管理内存。

Python 如何自动实现垃圾回收

Python 的垃圾回收是一个用于自动管理内存的复杂系统,使开发者无需为内存管理分心。Python 主要通过两种机制实现垃圾回收:引用计数与分代垃圾回收。这两种机制协同工作,确保高效的内存管理,最大限度减少内存泄漏,并优化应用性能。

在深入了解这些机制之前,需注意该垃圾回收过程主要与CPython(最常用的 Python 实现)相关。尽管其他实现如 PyPy 或 Jython 可能采用不同的垃圾回收策略,CPython 主要依靠这两种方法来确保应用顺畅运行。

引用计数

引用计数是 Python 管理内存的基础方法。其核心思想是跟踪内存中某个对象的引用(或指针)数量。每当创建一个指向该对象的新引用,Python 就会增加该对象的引用计数;相反,当引用被移除或超出作用域时,Python 会减少引用计数。其工作方式如下:

  • 跟踪引用: 每个 Python 对象都有一个引用计数,在对象被引用或取消引用时更新。例如,将对象赋给变量或将其传递给函数会增加引用计数,而删除该变量会降低引用计数。
  • 释放内存: 当对象的引用计数降为零(即您的代码中不再有任何地方使用该对象)时,Python 会自动释放其占用的内存。

尽管引用计数高效,但也有局限。最显著的问题是无法处理循环引用:当两个或多个对象相互引用形成环时,它们的引用计数永远不会降为零,内存因此无法回收。这正是分代垃圾回收发挥作用的地方。

分代垃圾回收

为克服引用计数的局限,Python 还采用分代垃圾回收。这一先进方法旨在处理循环引用并提升内存管理效率。其核心理念基于观察:大多数对象要么短命(临时),要么长命(持久)。通过按“年龄”为对象分代,Python 能更高效地进行回收。其工作方式如下:

  • 分代: Python 的垃圾回收器将对象组织为三代:第 0 代(最年轻)、第 1 代(中间)和第 2 代(最老)。新对象被放入第 0 代,若在回收后仍存活,则晋升到更高一代。

  • 优先处理年轻对象: 回收器更频繁地在年轻对象(第 0 代)上运行,因为它们更可能很快变为未使用。随着对象“变老”并移动到更高代,回收频率会降低。此策略通过将精力聚焦于更可能被丢弃的对象,减少了回收开销。

  • 处理循环引用: 分代垃圾回收在识别和回收涉及循环引用的对象方面尤为有效。在回收过程中,Python 的垃圾回收器可以检测这些环并回收内存,防止循环引用导致的内存泄漏。

如何手动触发 Python 垃圾回收

尽管 Python 的垃圾回收系统旨在自动处理内存管理,但在某些情况下,手动管理垃圾回收会有所帮助。下面一起看看。

在 Python 中主动触发回收

多数情况下,Python 的回收器会自动运行并清理未使用的对象,无需干预。但在某些场景下,您可能希望手动触发回收以释放内存。这在长时间运行的应用或内存密集型流程中尤为有用,因为这些情况下需要更精细地管理内存使用。

要在 Python 中手动触发垃圾回收,您可以使用 gc.collect() 函数。该函数会强制回收器立即运行,回收不再使用的内存。其工作方式如下:

import gc
# Trigger garbage collection manually
gc.collect()

当您调用 gc.collect() 时,Python 的回收器会执行一次完整回收,检查内存中的所有对象并释放那些不再被引用的对象。这在以下场景中尤其有用:

  • 内存密集型应用: 在处理大量数据或创建大量对象的应用中,可在关键节点手动触发回收以释放内存,降低内存膨胀的风险。
  • 长时间运行的进程: 对于长时间运行的服务或后台任务,手动管理回收可帮助维持稳定的内存占用,确保应用在长时间内仍保持响应与高效。

不过,需谨慎使用 gc.collect()。频繁手动回收会带来不必要的开销,因为该过程可能较为耗费资源。通常最好在已识别出潜在内存问题或确有需要在精确时刻释放内存的特定场景下使用。

禁用垃圾回收

在某些情况下,您可能希望暂时禁用 Python 的自动垃圾回收。这在实时应用、对性能要求极高的代码片段,或希望尽量减少中断的操作中可能有益,因为回收带来的开销可能影响性能。

可以使用 gc.disable() 函数禁用回收器:

import gc
# Disable automatic garbage collection
gc.disable()

禁用后,Python 将停止自动收集与释放未使用的对象。在某些场景下,这有助于获得更可预测的性能,避免回收器在关键操作期间意外运行。

但禁用垃圾回收也伴随风险:

  • 内存泄漏: 没有回收,未使用的对象会一直留在内存中直到进程结束,可能导致内存泄漏。对于长时间运行的应用,这会使内存占用不受控制地增长。
  • 循环引用: 在禁用回收器的情况下,循环引用无法被自动解决,若代码中存在环,将加剧内存问题。

因此,在性能关键的代码段执行完毕后,务必重新启用回收。可使用 gc.enable() 重新启用:

# Re-enable automatic garbage collection
gc.enable()

以下是手动垃圾回收的一些最佳实践:

  • 谨慎使用: 仅在必要时才进行手动回收,过度使用可能导致性能下降。
  • 结合剖析: 在手动触发或禁用回收前,考虑对应用进行内存剖析,了解其内存使用模式。这样可判断是否需要手动干预,以及在哪些位置效果最佳。
  • 及时恢复: 若禁用了回收,请在关键代码段结束后尽快重新启用,以确保长期内存管理的高效。

了解何时以及如何使用手动垃圾回收,能让您对应用的内存管理拥有更精细的掌控。这不仅有助于优化性能,也能预防影响应用稳定性的内存相关问题。

在下一部分,我们将探讨 Python 中常见的垃圾回收问题,并给出调试与解决的实用建议。

对 Python 开发者的实用建议

高效地管理内存对于编写高性能的 Python 应用至关重要。虽然 Python 的垃圾回收系统会自动处理大部分内存管理任务,但开发者仍可采取一些实用措施,避免常见陷阱并优化内存使用。本节将探讨避免内存泄漏、处理循环引用,以及在应用中管理大量对象的策略。

避免内存泄漏

当不再需要的对象未被正确释放时,就会发生内存泄漏,导致应用的内存占用随时间增长。这会引发性能下降,甚至导致应用崩溃。要监控内存中的对象,您可以使用 Python 的 gc 模块。通过查看内存中对象的数量,您可以发现是否存在意外增加,从而指示可能的内存泄漏:

import gc

# Get a list of all objects tracked by the garbage collector
all_objects = gc.get_objects()

print(f"Number of tracked objects: {len(all_objects)}")

如果您怀疑某个对象未被回收,gc.get_referrers() 函数可帮助识别是什么在使对象留在内存中。该函数返回引用给定对象的对象列表,便于您判断这些引用是否必要,或能否移除:

some_object = ...
referrers = gc.get_referrers(some_object)

print(f"Object is being referenced by: {referrers}")

另一种避免内存泄漏的方法是在缓存等场景使用弱引用。weakref 模块允许创建不会增加对象引用计数的引用:

import weakref

class MyClass:
    pass
obj = MyClass()
weak_obj = weakref.ref(obj)

print(weak_obj())  # Access the object

del obj  # Delete the strong reference
print(weak_obj())  # None, as the object is now collected

处理循环引用

当两个或多个对象相互引用形成闭环时,就会出现循环引用,使它们的引用计数永远无法降至零。如果回收器未能检测并收集这些对象,就可能导致内存泄漏。

Python 的分代回收器旨在处理循环引用,但最好一开始就避免不必要的环。使用 gc 模块,您可以通过触发一次回收并检查 gc.garbage 列表来检测参与引用环却未能回收的对象:

import gc

# Trigger garbage collection and get the number of uncollectable objects
gc.collect()

uncollectable_objects = gc.garbage
print(f"Number of uncollectable objects: {len(uncollectable_objects)}")

为防止循环引用,考虑在不再需要时显式移除引用。可以将引用设为 None,或使用弱引用以避免强引用环。此外,简化数据结构和对象关系的设计也能降低形成环的概率。

管理大量对象

在短时间内频繁创建与销毁大量对象会给 Python 的回收器带来压力。典型情形包括处理大型数据集、实时数据或执行复杂运算的应用。

一种思路是批量创建和销毁对象。与逐个创建/销毁相比,批量操作可降低回收频率,使回收器更高效地工作。例如:

objects = []

for i in range(1000):
    obj = SomeClass()
    objects.append(obj)

# Process all objects at once, then delete them
del objects[:]
gc.collect()  # Optionally trigger a manual garbage collection

另一点考虑是优化对象生命周期,尽量让对象要么“短命”要么“长命”。短命对象会很快被回收,长命对象会被提升到更高代,从而降低回收频率。

在对象被频繁创建与销毁的场景下,对象池是一种有效技术。对象池复用固定数量的对象,减轻回收器压力,并在内存受限环境中提升性能。以下是一个对象池实现示例:

class ObjectPool:
    def __init__(self, size):
        self.pool = [SomeClass() for _ in range(size)]
    def get(self):
        return self.pool.pop()
    def release(self, obj):
        self.pool.append(obj)

对象池在实时应用或游戏中尤为有益,这些场景对性能要求很高,而频繁创建与销毁对象的开销可能十分可观。

结论

本文介绍了 Python 的垃圾回收系统,从自动内存管理到可用于提升性能的手动干预。引用计数与分代垃圾回收的结合,使 Python 能够高效管理内存;同时,在某些情形下,手动介入也能带来收益。遵循本文给出的最佳实践,您可以避免内存泄漏、提升性能,并更好地掌控应用的内存处理方式。

如果您希望进一步学习更高级的内存管理与整体代码优化主题,DataCamp 提供了多种优质资源。您可以阅读我们的教程 How to Write Memory-Efficient Classes in Python 与 Memory Profiling in Python,有助于定位性能瓶颈。此外,学习如何编写更高效的 Python 代码始终对您的职业发展大有裨益。

Samuel Shaibu

作者

Samuel Shaibu

资深数据从业者与写作者,热衷于帮助有志于数据领域的专业人士成长与精进。

Python 垃圾回收常见问答

如何监控 Python 应用的内存使用?

可使用 memory_profiler、tracemalloc 和 objgraph 等工具。它们有助于跟踪内存消耗、识别内存泄漏并优化内存使用。

如何在 Python 中手动触发垃圾回收?

您可以使用 gc 模块来实现。例如 gc.collect() 可强制执行一次垃圾回收循环,在特定场景中有助于调试或优化内存使用。

可以禁用 Python 的垃圾回收吗?

可以,您可以通过 gc 模块禁用 Python 的垃圾回收。调用 gc.disable() 即可阻止回收器运行。

主题