Python的垃圾回收机制主要包括两个部分:引用计数和循环引用检测。
内部采用 引用计数法,为每个对象维护引用次数,并据此回收不在需要的垃圾对象。
由于引用计数法存在重大缺陷,循环引用时由内存泄露风险,因此Python还采用 标记清除法 来回收在循环引用的垃圾对象,循环引用检测。
此外,为了提高垃圾回收(GC)效率,Python还引入了 分代回收机制。
循环引用检测:
引用计数主要处理的是基本数据类型和简单的数据结构,如列表和字典等。然而,如果两个对象互相引用,即使它们被其他任何对象所引用,引用计数也不会减少到0,因为它们互相保持了对方的引用计数。为了处理这种情况,Python引入了一个循环检测器,该检测器通过定期执行一个循环垃圾回收的算法,找出并清除引用计数无法归零的对象。
循环检测器包括两个主要部分:一个是用于寻找所有可疑的循环引用的分代垃圾回收机制,另一个是用于确实并清除引用计数无法归零的对象的循环垃圾回收机制。
这两种机制一起工作,可以有效地回收Python中不再使用的对象,从而防止内存泄漏。
请注意,虽然这些是Python的默认垃圾回收机制,但是也可以通过Python的gc模块显式地控制垃圾回收的行为。
引用计数增加
a = [1,2]
b = [3,4]
a.append(b) #b的计数器2
b.append(a) #a的计数器2
del a
del b
引用计数法能够解决大多数垃圾回收的问题,但是遇到两个对象相互引用的情况,del语句可以减少引用次数,但是引用计数不会归0,对象也就不会被销毁,从而造成了内存泄漏问题。针对该情况,Python引入了标记-清除机制
引用计数这种管理内存的方式虽然很简单,但是有一个比较大的瑕疵,即它不能很好的解决循环引用问题。如上图所示:对象 A 和对象 B,相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减 1。因为对象 A 的销毁依赖于对象 B销毁,而对象 B 的销毁与依赖于对象 A 的销毁,这样就造成了我们称之为循环引用(Reference Cycle)的问题,这两个对象即使在外界已经没有任何指 针能够访问到它们了,它们也无法被释放。
遍历活跃对象,第一步需要找出 根对象 ( root object )集合。所谓根对象,就是指被全局引用或者在栈中引用的对象,这部分对象是不能被删除的。因此,我们将这部分对象标记为绿色,作为活跃对象遍历的起点。
根对象本身是 可达的 ( reachable ),不能删除;被根对象引用的对象也是可达的,同样不能删除;以此类推。我们从一个根对象出发,沿着引用关系遍历,遍历到的所有对象都是可达的,不能删除。而没有被标色的对象就是 不可达 ( unreachable )的垃圾对象,可以被安全回收。循环引用的致命缺陷完美解决了!
Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),对应的是3个链表,它们的垃圾收集频率随着对象的存活时间的增大而减小。
Python的垃圾回收机制不会保证立即释放内存,可能会在程序运行过程中或程序结束后执行。因此,虽然Python的垃圾回收机制可以帮助优化内存使用,减少内存泄漏和不必要的内存分配,但是开发者仍然需要注意编写高效的代码,避免不必要的内存消耗。