gc模块提供一个接口给开发者设置垃圾回收的选项,它的一个主要功能就是解决循环引用的问题。
常用函数:
1、gc.set_debug(flags) 设置gc的debug日志,一般设置为gc.DEBUG_LEAK
2、gc.collect([generation]) 显式进行垃圾回收,可以输入参数,0代表只检查0代的对象,1代表检查0、1代的对象,2代表检查0、1、2代的对象,如果不传参数,执行一个full collection,也就是等于传2。 返回不可达(unreachable objects)对象的数目
3、gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率。
4、gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率。
5、gc.get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
6 、gc.garbage()
gc模块的自动垃圾回收机制
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查+垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在0代中,如果在一次0代的垃圾检查中,该对象存活下来,就会被放到1代中,同理在一次1代的垃圾检查中,该对象存活下来,就会被放到2代中。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次0代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:
print(gc.get_count()) # (590, 8, 0)
a = ClassA()
print(gc.get_count()) # (591, 8, 0)
del a
print(gc.get_count()) # (590, 8, 0)
3是指距离上一次1代垃圾检查,0代垃圾检查的次数,同理,0是指距离上一次2代垃圾检查,1代垃圾检查的次数。
gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10) 每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器。
例如:
当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)
garbage
在Python中,gc.garbage是一个列表,用于存储循环垃圾收集器(Cycle GC)无法处理的循环引用对象。当循环垃圾收集器运行时,它会检测并清理不可达的循环引用对象,并将这些对象添加到gc.garbage列表中。
需要注意的是,gc.garbage列表中的对象通常是由于循环引用而无法被垃圾回收机制正常释放的。这些对象可能包含对其他资源的引用,如文件、网络连接等,如果不适当地处理这些对象,可能会导致资源泄漏。
可以通过遍历gc.garbage列表来检查并处理这些循环引用对象,例如关闭文件、断开网络连接等。以下是一个示例:
import gc
# 执行循环垃圾收集
gc.collect()
# 检查并处理gc.garbage中的对象
for obj in gc.garbage:
# 处理obj对象,例如关闭文件或断开网络连接
# ...
# 清空gc.garbage列表
del gc.garbage[:]
循环引用导致内存泄漏的原因:
del语句主要用于删除变量的引用,而无法直接处理对象内部的间接引用。del语句可以解除变量与对象之间的直接引用关系,从而使得该变量无法再访问该对象。但是,如果对象内部存在循环引用,即使删除了所有直接引用,垃圾回收机制也无法处理这种情况。
因为Python的垃圾回收机制主要基于引用计数,对于循环引用中的对象,即使没有任何外部引用指向它们,它们之间的引用计数仍然不为0,从而无法被垃圾回收机制及时释放内存。
为了处理循环引用问题,Python的垃圾回收机制会使用其他算法,如分代垃圾回收(Generational Garbage Collection),通过检测和清理无法通过引用计数解决的循环引用问题。这些算法可以识别循环引用并及时清理内存,避免内存泄漏的发生。
总结起来,del语句主要用于删除变量的引用,而无法直接处理对象内部的间接引用。对于循环引用问题,需要依赖Python的垃圾回收机制来处理。
del 的作用:
在Python中,del语句并不是简单地将变量的引用计数减1。它的作用是删除对变量的引用,使得该变量无法再被访问。这意味着,当执行del 变量名时,Python会做以下操作:
- 如果该变量是当前作用域下的变量,则将其从命名空间中删除。
- 如果该变量是最后一个引用,即没有其他引用指向相同的对象,则释放该对象所占用的内存空间。
- 如果该变量是对象的最后一个引用,并且该对象有一个__del__()方法,则调用该方法。
值得注意的是,del语句只是删除变量的引用,并不会立即释放对象占用的内存空间,而是由Python的垃圾回收机制负责在适当的时候释放这些内存。因此,
即使执行了del语句,对象也不一定会立即被销毁,而是会等待垃圾回收器的下一次运行来处理。
下面是一个示例代码说明循环引用导致的内存泄漏问题:
class Person:
def __init__(self, name):
self.name = name
self.friend = None
def set_friend(self, friend):
self.friend = friend
def create_objects():
john = Person("John")
mike = Person("Mike")
john.set_friend(mike)
mike.set_friend(john)
create_objects()
在上面的示例中,Person类表示一个人,每个人都有一个朋友对象。在create_objects函数中,我们创建了两个Person对象john和mike,并将它们互相设置为朋友。由于彼此引用,这两个对象形成了一个循环引用。
当create_objects函数执行完毕时,这两个对象不再被使用,但由于它们之间的循环引用,它们无法被垃圾回收机制清理。这就导致了内存泄漏,即这些对象占用的内存无法被释放。
方法一:
可以使用weakref模块来创建弱引用,从而打破循环引用。修改上述示例代码如下:
import weakref
class Person:
def __init__(self, name):
self.name = name
self.friend = None
def set_friend(self, friend):
self.friend = weakref.proxy(friend)
def create_objects():
john = Person("John")
mike = Person("Mike")
john.set_friend(mike)
mike.set_friend(john)
create_objects()
方法二:
class Person:
def __init__(self, name):
self.name = name
self.friend = None
def set_friend(self, friend):
self.friend = friend
def create_objects():
john = Person("John")
mike = Person("Mike")
john.set_friend(mike)
mike.set_friend(john)
john.friend = None # 业务逻辑角度上不合理,但此处只是为了说明该方法可以解决循环引用导致的内存泄漏
mike.friend = None
create_objects()

本文深入探讨Python的垃圾回收机制,包括gc模块的功能、循环引用问题的解决方法及垃圾回收的自动触发条件。介绍了gc模块提供的接口,如gc.collect()、gc.set_threshold()等,并解释了如何使用gc.garbage处理循环引用导致的内存泄漏。
335





