Python的垃圾回收机制
Python的垃圾回收机制
对象:因为Python中一切皆对象,所以变量的本质其实就是对象的一个指针;比如:a = 1
其实先分配内存(创建对象)存储数据“1”;然后a变量中指向对象(变量a存储着对象的内存地址)
不可变对象:变量相对于对象的指向不会发生改变
1.具有相同值的不可变对象,变量指向的是同一个对象
2.只要不可变对象的值发生改变,变量就会指向重新创建的对象
可变对象:变量相对于对象的指向会发生改变
1.具有相同值的可变对象,变量指向的是不同的对象
2.允许可变对象存储数据的值发生改变而不影响变量的指向
因为Python运行过程中会使用各种变量,所以如果对象的占用内存管理不当,程序会由于内存溢出而异常终止
1.引用计数法
Python中采用引用计数
为主,标记-清除
和分代回收
为辅的策略
引用计数法:每当对象被引用一次引用计数
就会加一;而当引用被销毁后就会进行减一,当引用计数
为零时进行内存回收
优点
1.高效且易于实现
2.实时性:一旦没有引用,内存直接释放
3.对象拥有确定的周期
缺点
1.资源消耗:维护引用计数的次数和引用赋值成正比
2.无法解决循环引用问题
引用计数案例
import sys
class A():
def __init__(self):
'''初始化对象'''
print('object born id:%s' %str(hex(id(self))))
def f1():
'''循环引用变量与删除变量'''
while True:
c1=A()
del c1
def func(c):
print('obejct refcount is: ',sys.getrefcount(c)) #getrefcount()方法用于返回对象的引用计数
if __name__ == '__main__':
#生成对象
a=A()
func(a)
#增加引用
b=a
func(a)
#销毁引用对象b
del b
func(a)
注意:查看对象的引用计数使用python内置模块中的sys的getrefcount()方法
运行结果:
object born id:0xfbfed0
obejct refcount is: 4
obejct refcount is: 5
obejct refcount is: 4
导致引用计数 +1 的情况
-
对象被创建,例如 a=23
-
对象被引用,例如 b=a
-
对象被作为参数,传入到一个函数中,例如
func(a)
-
对象作为一个元素,存储在容器中,例如
list1=[a,a]
导致引用计数-1 的情况
-
对象的别名被显式销毁,例如
del a
-
对象的别名被赋予新的对象,例如
a=24
-
一个对象离开它的作用域,例如 f 函数执行完毕时,
func
函数中的局部变量(全局变量不会) -
对象所在的容器被销毁,或从容器中删除对象
2.标记-清除
标记-清除
1)它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。
2)对象之间通过引用(指针)连在一起,构成一个有向图
3)从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象,根对象就是全局变量、调用栈、寄存器。
注:像是PyIntObject、PyStringObject这些不可变对象是不可能产生循环引用的,因为它们内部不可能持有其它对象的引用。
- 在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3,程序无法访问块4和5
- 第一步将标记块1,并记住块2和3以供稍后处理。
- 第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。
- 扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。
3.分代回收
1、Python将内存分为了3“代”,分别为年轻代、中年代、老年代
2、最初创建对象都放在年轻代
3、当触发一次标记清除时,如果没有被清除,从年轻代到中年代
4、再一次触发标记清除,在中年代的数据依然没有被清除,就会放到老年代
5、意义:越多次数无法清除的,以后被清除的概率就更低
参考博客: https://testerhome.com/topics/16556