【Python学习】引用计数和垃圾回收

引用计数

python的垃圾回收采用的是引用计数机制为主和分代回收机制为辅的结合机制,当对象的引用计数变为0时,
对象将被销毁,除了解释器默认创建的对象外。(默认对象的引用计数永远不会变成0)

所有的计数引用+1的情况:

一.对象被创建:
1.a = 23
  这里23这个对象并没有在内存中新建,因为在Python启动解释器的时候会创建一个小整数池,-5~256之间的这些对象
会被自动创建加载到内存中等待调用;a = 23是为23这个整数对象增加了一个引用。
执行代码:

>>> import sys
>>> a = 23
>>> sys.getrefcount(a)

结果:15

23这个整数对象目前有15个引用。

2.MyName()

class MyName(object):
pass

以上,如果对象被创建后没有引用操作,此时的引用计数是0,MyName()本身不是一个引用。

print(sys.getrefcount(MyName()))

结果:1

说明:之所以结果为1,是因为sys.getrefcount(MyName())函数也算一个引用。

二.对象被引用;

a = 23345455
b = a
c = b
print(sys.getrefcount(b))
print(sys.getrefcount(c))

结果:4,4

说明:每一次赋值都会增加数据操作的引用次数,要记住引用的是变量a,b,c等指向的数据23345455,而不是变量本身。

三.对象被作为参数,传入到一个函数中;

# 增加了一个引用
a = 23345455
# 增加了一个引用
b = a
# 增加了一个引用
c = b
# 增加了一个引用
print(sys.getrefcount(b))  # 执行完毕后引用销毁,减少一个引用
# 增加了一个引用
print(sys.getrefcount(c))  

说明:以上代码,赋值操作为数据增加了3个引用,sys.getrefcount(b)也增加了一个引用;

为什么sys.getrefcount(c)的结果还是4呢?这是因为当函数执行后,作为参数的引用会自动销毁,所以print(sys.getrefcount(b))在执行完毕后引用就删除了

四.对象作为一个元素,存储在容器中;

# 增加了一个引用
a = 23345455
# 增加了一个引用
b = a
list = [a, b]  # 增加了2个引用
print(sys.getrefcount(b)

结果:5

python所有对象引用计数被减少1的情况:

一.对象的别名被赋予新的对象;

a = 23345455  # 增加了一个引用
b = a  # 增加了一个引用
print(sys.getrefcount(a))
b = 1.4  # 减少了一个23345455整数的引用
print(sys.getrefcount(a))
结果:3;2
二.对象的别名被显式销毁;
a = 23345455  # 增加了一个引用
b = a  # 增加了一个引用
list = [a, b]  # 增加了2个引用
del a
print(sys.getrefcount(b))
结果:4

说明:直接使用del关键字或者del()函数;注意:上述代码手动销毁的是被赋值引用的a,但是在列表里的a不会被销毁。

三.一个对象离开它的作用域;

a = 23345455  # 增加了一个引用
b = a  # 增加了一个引用
print(sys.getrefcount(a))  # 执行完毕后引用销毁
print(sys.getrefcount(a))

结果:3;3

说明:a作为参数传递到sys.getrefcount(a)函数中,只在函数中起作用,一旦执行完毕就会销毁。

四.对象所在的容器被销毁,或从容器中删除对象;

# 增加了一个引用
a = 23345455
# 增加了一个引用
b = a
list = [a, b]  # 增加了2个引用
del list
print(sys.getrefcount(b))

结果:3

以下转载于:https://www.cnblogs.com/cccy0/p/9061799.html

python的垃圾回收机制是以引用计数为主,加上标记-清除,分代收集等辅助方式组成的,如果想打开gc功能,需要 import gc 模块 ,然后 gc.enable() 就打开了这个功能,关闭是 gc.disable() .

查看一个对象的引用计数: sys.getrefcount()    总是会比实际+1 ,因为 sys.getrefcount() 也调用了它一次 .

引用计数被+1的情况:

一.对象被创建

二.对象被引用

三.对象被作为参数,传入到一个函数中

四.对象作为一个元素,存储在容器中

引用计数被-1的情况:

一.对象的别名被赋予新的对象

二.对象的别名被显式销毁

三.一个对象离开它的作用域

四.对象所在的容器被销毁,或从容器中删除对象

垃圾回收
1.被引用为0时,立即回收当前对象

2.达到了垃圾回收的阈值,触发标记-清除

3.手动调用gc.collect()

4.Python虚拟机退出的时候



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.set_threshold(threshold0[, threshold1[, threshold2])
设置自动执行垃圾回收的频率。
4、gc.get_count()
获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表

5、gc模块的自动垃圾回收机制
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查+垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。

gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:

复制代码
print gc.get_count() # (590, 8, 0)
a = ClassA()
print gc.get_count() # (591, 8, 0)
del a
print gc.get_count() # (590, 8, 0)
复制代码
3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。

gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10)
每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器
例如,假设阀值是(700,10,10):

当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)
其他
如果循环引用中,两个对象都定义了__del__方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法,所以为了安全起见,gc模块会把对象放到gc.garbage中,但是不会销毁对象。
五.应用

项目中避免循环引用
引入gc模块,启动gc模块的自动清理循环引用的对象机制
由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的__del__来打破僵局
作者:gtea 博客地址:https://www.cnblogs.com/gtea
原文地址:https://www.cnblogs.com/gtea/p/13027466.html