python采用的是引用计数机制为主,分代收集机制为辅的策略
1. 小整数对象池
整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。
Python 对小整数的定义是 [-5, 256] 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象.
同理,单个字母也是这样的。
2. 大整数对象池
每一个大整数,均创建一个新的对象。
3. intern机制
靠引用计数去维护何时释放。
总结
- 小整数[-5,256]共用对象,常驻内存
- 单个字符共用对象,常驻内存
- 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
- 字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
- 大整数不共用内存,引用计数为0,销毁
- 数值类型和字符串类型在 Python 中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象
引用计数机制的优点:
- 简单
- 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
- 维护引用计数消耗资源
- 循环引用
Python中的GC阈值,分代计数检测循环引用
python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表:
Python会循环遍历零代列表上的每个对象,找出列表中每个互相引用的对象,根据规则减掉其引用计数
通过识别内部引用,Python能够减少许多零代链表对象的引用计数
因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。
而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。
垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅。
1、导致引用计数+1的情况
对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a]
2、导致引用计数-1的情况
对象的别名被显式销毁,例如del a
对象的别名被赋予新的对象,例如a=24
一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象
3、查看一个对象的引用计数
import sys
a = "hello world"
sys.getrefcount(a)
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
垃圾回收:
class ClassA(): def __init__(self): print('object born,id:%s'%str(id(self))) def f2(): while True: c1 = ClassA() c2 = ClassA() c1.t = c2 c2.t = c1 del c1 del c2 gc.collect()#手动调用垃圾回收功能,这样在自动垃圾回收被关闭的情况下,也会进行回收 #python默认是开启垃圾回收的,可以通过下面代码来将其关闭 gc.disable() f2()
有三种情况会触发垃圾回收:
- 当gc模块的计数器达到阀值的时候,自动回收垃圾
- 调用gc.collect(),手动回收垃圾
- 程序退出的时候,python解释器来回收垃圾
gc模块的自动垃圾回收触发机制
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在零代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到一代中,同理在一次一代的垃圾检查中,该对象存活下来,就会被放到二代中。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
查询收集器列表
import gc print(gc.get_count()) (173, 4, 1)