python的垃圾回收机制:内存管理的幕后英雄

图片

一、什么是垃圾回收机制?

在Python程序运行过程中,会不断创建各种对象,如列表、字典、类实例等。当这些对象不再被使用时,就成了 “垃圾”,占用的内存空间需要被释放,以便后续程序使用。垃圾回收机制(Garbage Collection,简称 GC)是Python中自动管理内存、回收 “垃圾” 对象的一套机制。

二、Python 垃圾回收核心原理

1. 引用计数

原理:Python 为每个对象维护一个引用计数,记录该对象被引用的次数。

当对象被创建时,引用计数为 1;

每次被引用(如赋值给变量、作为参数传递等),引用计数加 1;

当引用失效(如变量被删除、函数执行结束),引用计数减 1。

当引用计数为 0 时,对象占用的内存会被立即回收。

案例

a = [1, 2, 3] # 对象[1, 2, 3]的引用计数为1
b = a         # 对象[1, 2, 3]的引用计数变为2
del a         # 对象[1, 2, 3]的引用计数减为1
b = None      # 对象[1, 2, 3]的引用计数变为0,内存被回收

2. 标记 - 清除

原理:对于循环引用(即对象之间相互引用,导致引用计数无法降为0)的情况,Python 采用标记-清除算法。

首先从根对象(如全局变量、栈上的变量等)开始 “标记” 所有可达的对象,这些对象是程序仍在使用的;然后扫描整个内存空间,清除所有未被标记的对象,即 “垃圾” 对象。

案例

class Node:
    def__init__(self):
        self.next = None
a = Node()
b = Node()
a.next = b
b.next = a # a和b循环引用
del a
del b # 此时a和b的引用计数仍为1,通过标记 - 清除算法回收内存

3. 分代回收

原理:Python将对象分为三代,根据对象存活时间的不同进行管理。新创建的对象位于第0代,当第0代对象经过一定次数的垃圾回收后仍存活,会被移到第1代;同理,第1代对象经过多次回收存活后,会移到第2代。垃圾回收时,优先回收第0代对象,因为新创建的对象更有可能很快变成 “垃圾”,这样可以提高回收效率。

三、与垃圾回收相关的函数及案例

1. sys.getrefcount()

功能:获取对象的引用计数。注意,调用该函数时会临时增加一次引用计数(因为函数调用本身也是对对象的引用),所以返回值比实际引用计数多 1。

案例

import sys
a = [10, 20]
print(sys.getrefcount(a)) # 输出比实际引用计数多1
b = a
print(sys.getrefcount(a)) # 引用计数增加

2. gc.get_threshold() 和 gc.set_threshold()

功能

  • gc.get_threshold():获取分代回收中各代对象的回收阈值,返回一个三元组,分别对应第0代、第1代、第2代的阈值。

  • gc.set_threshold(threshold0[, threshold1[, threshold2]]):设置分代回收中各代对象的回收阈值。

案例

import gc
# 获取当前回收阈值
print(gc.get_threshold())
# 设置新的回收阈值
gc.set_threshold(700, 10, 10)
print(gc.get_threshold())

3. gc.collect()

功能:手动触发垃圾回收,无论当前对象的引用计数和代龄如何,立即执行垃圾回收操作。

案例

import gc
class BigObject:
    def__init__(self):
	self.data = [i for i in range(1000000)]

# 创建对象,占用大量内存
obj = BigObject()
del obj # 此时对象成为垃圾,但不一定立即回收
# 手动触发垃圾回收
gc.collect()

四、循环引用场景下查看对象引用情况

1.先不构建循环引用

class Node:
    def__init__(self):
        self.next = None
#实例化Node对象
a = Node()
b = Node()
#删除对象
del a
del b

#获取所有对象
all_objects = gc.get_objects()
for obj in all_objects:
    if isinstance(obj, Node):
        print(f"找到Node对象,地址: {id(obj)}")
#以上代码执行无日志打印,说明Node对象已经不存在。

2. 构建循环引用案例

class Node:
    def__init__(self):
        self.next = None
#实例化Node对象
a = Node()
b = Node()
#循环引用
a.next = b
b.next = a
#删除对象
del a
del b

#获取所有对象
all_objects = gc.get_objects()
for obj in all_objects:
    if isinstance(obj, Node):
        print(f"找到Node对象,地址: {id(obj)}")
#以上代码执行有日志打印,说明Node对象还是存在。
找到Node对象,地址: 4452874272
找到Node对象,地址: 4455658736

在这个例子里,a和b对象相互引用,形成了循环引用。通过gc.get_objects()获取所有对象,筛选出Node类型对象,可发现循环引用的对象。

当我们进行手工回收后,发现循环引用的对象已经不存在了:

class Node:
    def__init__(self):
        self.next = None

a = Node()
b = Node()
a.next = b
b.next = a
del a
del b

# 强制进行垃圾回收,查看是否能回收对象
gc.collect()

all_objects = gc.get_objects()
for obj in all_objects:
    if isinstance(obj, Node):
        print(f"找到Node对象,地址: {id(obj)}")
#程序执行后,已经没有Node对象了。

五、总结

Python的垃圾回收机制是保障程序稳定运行、高效利用内存的重要组成部分。从基础的引用计数到复杂的标记-清除、分代回收,再到相关函数的使用,每一个环节都有其独特的作用。避开常见的 “坑”,合理利用垃圾回收机制,能让你的Python 代码在内存管理上更加得心应手!

图片

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐