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
二.循环引用导致内存泄露
引用计数的缺陷是循环引用的问题
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
def f2():
while True:
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
del c1
del c2
#把python的gc关闭
gc.disable()
f2()
执行f2(),进程占用的内存会不断增大。
- 创建了c1,c2后这两块内存的引用计数都是1,执行c1.t=c2和c2.t=c1后,这两块内存的引用计数变成2.
- 在del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del
c2后,同理,内存1的对象,内存2的对象的引用数都是1。 - 虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。
垃圾回收
#coding=utf-8
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
# def __del__(self):
# print('object del,id:%s'%str(hex(id(self))))
def f3():
print("-----0------")
# print(gc.collect())
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
print("-----1------")
del c1
del c2
print("-----2------")
print(gc.garbage)
print("-----3------")
print(gc.collect()) #显式执行垃圾回收
print("-----4------")
print(gc.garbage)
print("-----5------")
if __name__ == '__main__':
gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
f3()
python2运行结果:
—–0——
object born,id:0x724b20
object born,id:0x724b48
—–1——
—–2——
[]
—–3——
gc: collectable
gc: collectable
gc: collectable
gc: collectable
4
—–4——
[<main.ClassA instance at 0x724b20>, <main.ClassA instance at 0x724b48>, {‘t’: <main.ClassA instance at 0x724b48>}, {‘t’: <main.ClassA instance at 0x724b20>}]
—–5——
说明:
- 垃圾回收后的对象会放在gc.garbage列表里面
- gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict
有三种情况会触发垃圾回收
- 调用gc.collect(),
- 当gc模块的计数器达到阀值的时候。
- 程序退出的时候