在 Python 应用开发中,我们往往专注于业务逻辑的实现,而忽略了隐藏在背后的Python 中的垃圾回收机制。但当项目规模变大,并发量增高,内存泄露等问题开始出现时,深入理解 GC (Garbage Collection) 原理就显得至关重要。例如,线上服务使用 uWSGI 部署,并发连接数较高的情况下,如果 GC 不当,会导致 CPU 占用率飙升,影响用户体验。
引用计数:GC 的基石
Python 采用引用计数作为主要的垃圾回收机制。每个对象都有一个引用计数器,当对象被引用时,计数器加一;当引用解除时,计数器减一。当计数器归零时,对象所占用的内存会被释放。
import sys
a = [1, 2, 3] # 创建一个列表,引用计数为 1
b = a # b 引用了 a,引用计数增加到 2
print(sys.getrefcount(a)) # 输出 3 (包括 getrefcount 本身的引用)
del a # 解除 a 的引用,引用计数减 1
print(sys.getrefcount(b)) # 输出 2 (包括 getrefcount 本身的引用)
del b # 解除 b 的引用,引用计数减 1,列表对象被回收
引用计数简单高效,但无法解决循环引用的问题。考虑以下情况:
class Node:
def __init__(self):
self.next = None
a = Node()
b = Node()
a.next = b # a 引用 b
b.next = a # b 引用 a,形成循环引用
del a # 解除 a 的引用
del b # 解除 b 的引用
# 此时 a 和 b 的引用计数都为 1,但它们已经无法被访问,造成内存泄露
标记清除:解决循环引用
为了解决循环引用问题,Python 引入了标记清除机制。它定期扫描内存中的对象,从根对象(例如全局变量、栈上的对象)出发,标记所有可达的对象。未被标记的对象即为垃圾,会被清除。
标记清除算法通常在特定的时间间隔或内存占用达到阈值时触发。可以使用 gc 模块手动控制 GC 的行为。
import gc
# 获取当前 GC 设置
print(gc.get_threshold())
# 手动执行垃圾回收
gc.collect()
# 调整 GC 阈值
gc.set_threshold(700, 10, 10)
GC 阈值 (threshold) 决定了何时触发垃圾回收。gc.get_threshold() 返回的是一个三元组 (threshold0, threshold1, threshold2),分别代表:
threshold0: 新生代对象数量达到该值时,触发 0 代 GC。threshold1: 0 代 GC 执行次数达到该值时,触发 1 代 GC。threshold2: 1 代 GC 执行次数达到该值时,触发 2 代 GC。
分代回收的思想是将对象分为不同的代,存活时间长的对象放在高代,减少扫描频率,提高效率。类似于 JVM 的新生代和老年代。
分代回收:提升 GC 效率
Python 的垃圾回收采用分代回收策略,将对象划分为三代:0 代、1 代和 2 代。新创建的对象属于 0 代,经过一次垃圾回收仍然存活的对象会被移到 1 代,以此类推。
高代的对象存活时间更长,因此垃圾回收的频率较低,从而提升了整体的 GC 效率。可以通过 gc.get_generation() 查看对象的代数。
性能优化与避坑指南
- 减少对象创建:避免在循环中频繁创建对象,尽量重用对象。 例如使用
string.join()拼接字符串,而不是+操作符。 - 手动解除引用:及时删除不再使用的对象,特别是包含循环引用的对象。可以使用
del语句或者将变量赋值为None。 - 合理调整 GC 阈值:根据应用的特点,调整 GC 阈值,避免频繁的 GC 或过长的 GC 间隔。可以使用
gc.set_threshold()进行设置。需要注意的是,过小的阈值会导致频繁 GC,消耗 CPU 资源;过大的阈值可能导致内存占用过高,甚至 OOM。 - 使用工具进行分析:使用
objgraph等工具分析内存占用情况,找出内存泄露的原因。 - 避免全局变量:全局变量的生命周期较长,容易造成内存占用。尽量使用局部变量,并及时释放。
- 注意循环引用:特别是在使用类和对象时,注意避免循环引用。可以使用弱引用 (weakref) 来打破循环引用。
在实际部署中,如果发现 Python 进程 CPU 占用率过高,可以考虑使用 py-spy 或 perf 等工具进行性能分析,找出导致性能瓶颈的代码。另外,使用 Nginx 作为反向代理服务器时,需要合理配置 worker_processes 和 worker_connections 参数,以充分利用多核 CPU 的性能。 同时,利用宝塔面板可以方便地监控服务器资源使用情况,例如 CPU、内存、磁盘 I/O 等。
理解 Python 的垃圾回收机制,并结合具体的应用场景进行优化,可以有效避免内存泄露,提升应用的性能和稳定性。希望本文能帮助你更深入地了解 Python 的垃圾回收机制,并在实际开发中避免一些常见的坑。
冠军资讯
加班到秃头