什么是垃圾回收
当为一个变量分配数据的时候,python会在内存中分配一部分空间,用户储存此数据,但内存空间总是有限的,如果一直占用内存空间,内存迟早会溢出,所以,程序中需要把无用的数据从内存中删除,回收内存空间,这个过程就叫作垃圾回收。
python采用的是引用计数机制为主,标记–>清除和分代收集(隔代回收)两种机制为辅的策略。
垃圾回收中的计数
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少
简单来说,当一个变量被赋值一次时,这个变量此时的计数为1,当这个变量被其它变量引用时,此时的计数将会加1,当引用它的对象被删除时,它的计数将会减少1,当计数为0时,此时的这个变量对应的数据则为垃圾数据,此数据就会被回收。
import sys
name = 'a'
print(sys.getrefcount(name)) # 返回值11,初始值为什么是11而不是1?后续需要查答案
name1 = name
print(sys.getrefcount(name)) # 返回值12,多引用了一次,计数器加1
name2 = name1
print(sys.getrefcount(name)) # 返回值13,再次引用,计数器加1
del name1
print(sys.getrefcount(name)) # 返回值12,删除一个变量,计数器减1
垃圾回收中的 标记->回收
计数问题产生了一个新的问题,就是循环引用无法回收,什么是循环引用,代码如下:
l1 = [1, 2] # 此时l1的计数假设为1
l2 = [3, 4] # 此时l2的计数假设为1
l1.append = l2 # 此时l2的计数为2
l2.append = l1 # 此时l1的计数为2
del l1 # 此时l1的计数为1
del l2 # 此时l2的计数为1
上面的代码可以明白,由于互相引用的情况下产生了循环引用,此时删除了l1与l2两个变量是无法让计数变为0的,那么内存就无法得到释放,为了解决这个问题,python出现了一个 标记 -> 回收机制。
当内存不够用时,python会去检查代码中的所有内存中的数据值,如果发现了一个循环引用的值时,就会打上标记,当python检查完内存中所有数据值时,将会一次性回收所有的被打过标记的数据值。
Python中的分代回收
分代回收是一种以空间换时间的机制。
- python会将内存对象根据存活时间划分为三个不同的集合。每个集合可以称为一个代。可以理解为“第0代”,“第1代”,“第2代”,他们对应的是3个链表,它们的垃圾收集频率随着对象存活时间的增大而减小。
- 新创建的对象都被分配到“第0代”中,当“第0代”的链表总数量达到上限时,python的垃圾回收机制将被触发,将回收可回收对象,并将不可回收对象放到“第1代”链表中,依次类推,“第2代”链表中的对象为存活时间最久的对象,甚至存活整个系统的生命周期内。
- 分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象。
而分代回收被创建的原因就是为了节省系统运行的资源,系统可能会频繁的去检查“第0代”链表中的对象,而对于“第1代”链表中的对象则相对来说不会太过频繁去检查,而“第2代”链表中的对象可能很长时间才会检查一次,以此来节省运算资源。