Python垃圾回收机制
垃圾回收机制
垃圾回收机制(Garbage Collection),简称GC,是Python解释器自带的机制,专门用来进行垃圾回收。
在定义一个变量时,会申请内存空间,当该变量使用完毕,也应该释放掉该变量所占用的内存空间,在C中需要程序员手动释放掉内存,而Python则由GC机制进行回收。
无论何种垃圾回收机制,一般都分为两个阶段:垃圾检测和垃圾回收。垃圾检测就是区分已分配内存中的“可回收”和“不可回收”内存。垃圾回收则是使操作系统重新掌握垃圾检测阶段所标识出来的“可回收”内存块。
所谓垃圾回收,并不是直接把这块内存的数据直接清空了,而是将使用权重新交给了操作系统,不会应用程序霸占了。
什么是垃圾
- 当一个变量调用完毕,且后续不再需要时,便是垃圾。
- 当指向该变量地址的变量名指向另一个地址时,原变量内存地址无法被访问,此时该变量也是垃圾。
Python定义变量原理
在Python中一切皆对象,内存空间分为栈区和堆区,其中:
栈区:
- 保存变量名与变量值的内存地址的关联关系
堆区:
- 保存实际的变量值,也是内存管理回收的地方
GC机制原理
- 引用计数
- 分代回收
- 标记清除
引用计数
引用计数原理是每个对象维护一个ob_refcnt,用来记录当前对象被引用的次数。该方式的优点是:一旦引用计数为0,则会直接被回收。
引用计数加1的情况:
- 对象被创建:x=10
- 对象被引用:y=x
- 对象被当做参数传入:func(x)
- 对象作为容器类型的元素
引用计数减1的情况:
- 显示销毁对象的引用:
del x
- 对象的引用指向其他对象:
x = 20
- 对象的引用离开了它的作用域,比如函数的局部变量,在函数执行完毕时,也会被销毁(除非取栈帧)
- 对象的引用所在的容器被销毁,或者从容器中删除等
查看引用计数:
使用sys.getrefcount(obj)
,但是由于对象会作为参数传进去,所以引用计数会+1。
直接引与间接引用:
直接引用:从栈区直接指向到实际值的引用。
间接引用:从栈区指向堆区后,再次引用才能到达实际存放值的内存,容器类型。
引用计数的缺陷:
循环引用:当两个容器类型对象,内部分别将对方添加为元素,此时即便删除了变量名,这两个对象的引用计数容不能为0。
l1 = []
l2 = []
l1.append(l2)
l2.append(l1)
del l1,l2
此时虽然没有通过栈区指向堆区,但它们彼此指向,导致引用计数不为0,如果程序一直运行的话,是有可能发生内存泄露的。
标记清除
标记清除就是为了解决“循环引用”的问题。其过程为
- 寻找根对象(root object)的集合,root object就是一些全局引用和函数栈的引用。
- 遍历root object集合,对每一个引用可以直接或间接访问到的对象标记为存活的对象,其余均为非存活对象,应该被清除。
- 遍历堆中的所有对象,对未被标记的对象全部清除掉。
分代回收
基于引用计数的回收机制,每次进行内存回收,都需要把对象的引用计数遍历一遍,这是非常耗时的,于是就引入了分代回收来提升效率,分代回收采用的是“空间换时间”策略。
分代
在经历多次扫描的情况下,都没有被回收的对象,那么就认为,该变量是常用变量,对其的扫描频率会降低。具体的原理为:
分代指的是根据存活时间来为变量划分不同等级,也就是不同的代,垃圾回收的扫描频率会随着“代”的存活时间增大而减小。
“代”可以想象成链表,属于同一个代的对象都被连接在同一个链表中。在Python中总共存在三条链表,说明所有的对象可以分为三代:零代、一代、二代。一个“代”就是一条可收集对象链表。
第0代链表最多可容纳700个对象,一旦超过700这个阈值,那么会立即出发垃圾回收机制。
1代链表和2代链表触发垃圾回收的条件又是什么呢?当0代链表触发了10次垃圾回收的时候,会触发一次1代链表的垃圾回收。当1代链表触发了10次垃圾回收的时候,会触发一次2代链表的垃圾回收。
在清理1代链表的时候,会顺带清理0代链表
在清理2代链表的时候,会顺带清理0代链表和1代链表
gc模块
gc模块底层就是gcmodule,该模块是用C写的,当python编译好时,就内嵌在解释器里面了。我们可以导入它,但是在Python安装目录上看不到。
gc.enable:开启垃圾回收
这个函数表示开启垃圾回收机制,默认是自动开启的。
gc.disable:关闭垃圾回收
import gc
# 关掉gc
gc.disable()
# 开启
gc.enable()