上文讲解了垃圾回收机制的原理,本文将通过示例来分析引用计数和循环引用。
目录
一、引用计数
上文已经提过,当引用计数达到0时,该对象被视为不再被使用,其占用的内存空间就会被回收。
1、示例
import sys
class MyClass:
def __init__(self, name):
self.name = name
def main():
# 创建一个对象,此时它的引用计数为 1
obj = MyClass("example")
print(sys.getrefcount(obj)) # 通常会打印出 2,因为 getrefcount 函数本身也会增加一次引用
# 创建一个新的引用到同一个对象,引用计数增加
ref = obj
print(sys.getrefcount(obj)) # 通常会打印出 3
# 删除一个引用,引用计数减少
del ref
print(sys.getrefcount(obj)) # 通常会打印出 2
# 当 obj 超出作用域(即 main 函数结束)时,它的引用计数会变为 0,对象会被回收
if __name__ == "__main__":
main()
2、说明
sys.getrefcount(obj)
函数返回的是对象obj
的引用计数,但需要注意的是,由于getrefcount
函数本身也会创建一个对obj
的临时引用,所以实际打印的引用计数可能会比你预期的多一个。- 当对象超出其作用域时(例如,函数返回后,函数内的局部变量就会被回收),对象的引用计数会减少。如果此时引用计数变为 0,对象就会被垃圾回收器回收。2、函数局部变量回收
3、强制清除引用
在某些特定情况下,想要“释放”或“减少”Python 对象占用的内存。虽然不能直接“释放”一个对象的内存(就像 C 或 C++ ),但可以通过以下方式来影响 Python 的内存使用:
-
删除对象引用:
当不需要某个对象时,可以删除所有指向它的引用。这样,Python 的垃圾回收器就会在适当的时候回收该对象占用的内存。
obj = [1, 2, 3, 4] # 创建一个列表对象 del obj # 删除对列表的引用,当没有其他引用时,列表对象可能会被回收
- 使用
gc
模块:
Python 提供了 gc
模块,它允许我们与 Python 的垃圾回收器进行交互。可以使用 gc.collect()
来强制进行垃圾回收,但是一般不需要手动执行,因为 Python 的垃圾回收器会自动运行。
import gc
# ... 创建和删除一些对象 ...
gc.collect() # 强制进行垃圾回收
二、循环引用
两个对象互相引用,但是没有其他对象引用了,但是引用计数不为0,也不会被垃圾回收清空。
1、示例
import gc
import os
import psutil
import time
def create_cyclic_references():
# 获取当前进程ID
pid = os.getpid()
# 获取创建循环引用前的内存使用情况
process = psutil.Process(pid)
memory_before = process.memory_info().rss / (1024 ** 3) # 转换为GB
print(f"Memory usage before creating cyclic references: {memory_before:.2f} GB")
# 初始化两个列表并创建循环引用
a = [i for i in range(10000000)]
b = [i for i in range(10000000)]
a.append(b)
b.append(a)
# 等待一段时间以便垃圾回收器有机会运行(尽管循环引用不会被回收)
time.sleep(1)
# 获取创建循环引用后的内存使用情况
memory_after = process.memory_info().rss / (1024 ** 3) # 转换为GB
print(f"Memory usage after creating cyclic references: {memory_after:.2f} GB")
# 调用函数以创建循环引用并查看内存差异
create_cyclic_references()
# 注意:由于循环引用,Python的垃圾回收器不会回收a和b,
# 但在函数外部我们打印了函数返回的final_memory_usage
# 等待一段时间以便垃圾回收器有机会运行(尽管循环引用不会被回收)
time.sleep(1)
# 获取函数调用结束后的内存使用情况
pid = os.getpid()
process = psutil.Process(pid)
final_memory_usage = process.memory_info().rss / (1024 ** 3) # 转换为GB
print(f"Memory usage after function call: {final_memory_usage:.2f} GB")
gc.collect() # 强制回收
final_memory_usage = process.memory_info().rss / (1024 ** 3) # 转换为GB
print(f"Memory usage after gc.collect(): {final_memory_usage:.2f} GB")
2、说明
刚调用函数是0.01g,初始化数据后是0.39g,函数调用结束还是0.39g。但是函数调用已经结束,参数a和b应该被回收才对,但是因为互相引用,导致引用数不为0,无法被回收。最后强制回收之后,内存变回了0.01g。
在这里为了演示采用了强制回收,但是实际上python使用了标记-清除算法和分代收集算法来解决这种情况。
标记-清除算法和分代收集算法的原理可以看上一篇文章。