python中的垃圾回收机制
1.垃圾回收
1.) 小整数对象池 #提前建立好的
Python 对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象.
In [1]: a = 100 In [2]: b = 100 In [3]: c = 100 In [4]: id(a) Out[4]: 10922592 In [5]: id(b) Out[5]: 10922592 In [6]: id(c) Out[6]: 10922592
2). 大整数对象池
每一个大整数,均创建一个新的对象。
In [7]: A = 10000 In [8]: B = 10000 In [9]: id(A) Out[9]: 139945960905136 In [10]: id(B) Out[10]: 139945960905008
3). intern机制 #共享
In [11]: a = "hello" In [12]: b = "hello" In [13]: id(a) Out[13]: 139945942824976 In [14]: id(b) Out[14]: 139945942824976
## 带有空格的不共享引用 In [15]: a = "hello world" In [16]: b = "hello world" In [17]: id(a) Out[17]: 139945942858352 In [18]: id(b) Out[18]: 139945942921648
总结
- 小整数[-5,257)共用对象,常驻内存
- 单个字符共用对象,常驻内存
- 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
- 字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
- 大整数不共用内存,引用计数为0,销毁
- 数值类型和字符串类型在 Python 中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象
2. Garbage collection(GC垃圾回收)
python采用的是引用计数机制为主,隔代回收为辅的策略
1)引用计数机制
引用计数机制的优点: 简单 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。 引用计数机制的缺点:
引用计数机制的缺点: 维护引用计数消耗资源 循环引用
class ClassA(): def __init__(self): print('id:%s'%str(hex(id(self)))) def f2(): while True: c1 = ClassA() c2 = ClassA() c1.t = c2 c2.t = c1 del c1 del c2 gc.disable() #关闭gc功能 f2()
id:0x7f7cc240eb00 id:0x7f7cc240eb38 id:0x7f7cc240eb70 id:0x7f7cc240eba8 id:0x7f7cc240ebe0 id:0x7f7cc240ec18 id:0x7f7cc240ec50 id:0x7f7cc240ec88 id:0x7f7cc240ecc0 id:0x7f7cc240ecf8 id:0x7f7cc240ed30 id:0x7f7cc240ed68 id:0x7f7cc240eda0
2) 隔代回收,相互引用的减一
随后,Python会循环遍历零代列表上的每个对象,检查列表中每个互相引用的对象,根据规则减掉其引用计数。
在这个过程中,Python会一个接一个的统计内部引用的数量以防过早地释放对象。
为了便于理解,来看一个例子: (相互引用的减一)
从上面可以看到 ABC 和 DEF 节点包含的引用数为1.有三个其他的对象同时存在于零代链表中,蓝色的箭头指示了有一些对象正在被零代链表之外的其他对象所引用。
(接下来我们会看到,Python中同时存在另外两个分别被称为一代和二代的链表)。这些对象有着更高的引用计数因为它们正在被其他指针所指向着。
接下来你会看到Python的GC是如何处理零代链表的。
通过识别内部引用,Python能够减少许多零代链表对象的引用计数。在上图的第一行中你能够看见ABC和DEF的引用计数已经变为零了,这意味着收集器可以释放它们并回收内存空间了。
剩下的活跃的对象则被移动到一个新的链表:一代链表。
从某种意义上说,Python的GC算法类似于Ruby所用的标记回收算法。周期性地从一个对象到另一个对象追踪引用以确定对象是否还是活跃的,正在被程序所使用的,这正类似于Ruby的标记过程。
3.)Ruby的标记-清除机制
标记-清除
内部这一切发生得迅雷不及掩耳,因为Ruby实际上不会吧对象从这拷贝到那。而是通过调整内部指针,将其指向一个新链表的方式,来将垃圾对象归位到可用列表中的。
现在等到下回再创建对象的时候Ruby又可以把这些垃圾对象分给我们使用了。在Ruby里,对象们六道轮回,转世投胎,享受多次人生。
3.gc模块
采用引用计数的方法管理内存的一个缺陷是循环引用,
而gc模块的一个主要功能就是解决循环引用的问题。
常用函数: 1、gc.set_debug(flags) 设置gc的debug日志,一般设置为gc.DEBUG_LEAK 2、gc.collect([generation]) 显式进行垃圾回收,可以输入参数,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的列表
Out[18]: (676, 7, 5) In [19]: gc.get_count() Out[19]: (680, 7, 5) In [20]: gc.get_count() Out[20]: (9, 8, 5) In [21]: gc.get_count() Out[21]: (10, 8, 5)
###阀值是(700,10,10): In [25]: gc.get_threshold() Out[25]: (700, 10, 10)
3)、查看一个对象的引用计数
In [29]: import sys In [30]: a = "hello world" In [31]: sys.getrefcount(a) Out[31]: 2 In [32]: b = a In [33]: sys.getrefcount(a) Out[33]: 3 In [34]: c = a In [35]: sys.getrefcount(a) Out[35]: 4
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
有三种情况会触发垃圾回收:
- 调用gc.collect(),
- 当gc模块的计数器达到阀值的时候。
- 程序退出的时候
注意点
gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法
import gc class ClassA(): pass # def __del__(self): # print('object born,id:%s'%str(hex(id(self))))