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 代码在内存管理上更加得心应手!