前言
在上一篇文章[python 进阶] Garbage collection 垃圾回收 1, 我们讲述了 Garbage collection(GC 垃圾回收), 画说 Ruby 与 Python 垃圾回收, Python 中的循环数据结构以及引计数以及 Python 中的 GC 阈值, 这一节我们将继续介绍 GC 模块的一些应用和注意事项, 下面开始今天的讲解~~
一, 垃圾回收机制
Python 中的垃圾回收是以引计数为主, 分代收集为辅.
1, 导致引计数 + 1 的情况
对象被创建, 例如 a=23
对象被引, 例如 b=a
对象被作为参数, 传到个函数中, 例如 func(a)
对象作为个元素, 存储在容器中, 例如 list1=[a,a]
2, 导致引计数 - 1 的情况
对象的别名被显式销毁, 例如 del a
对象的别名被赋予新的对象, 例如 a=24
个对象离开它的作域, 例如 f 函数执完毕时, func 函数中的局部变量(全局变量不会)
对象所在的容器被销毁, 或从容器中删除对象
3, 查看个对象的引计数
- In [1]: import sys
- In [2]: a = "hello world"
- In [3]: sys.getrefcount(a)
- Out[3]: 2
可以查看 a 对象的引计数, 但是正常计数 1, 因为调函数的时候传 a, 这会让 a 的引计数 + 1
二, 循环引导致内存泄露
引计数的缺陷是循环引的问题
- import sys
- a = "hello world"
- sys.getrefcount(a)
- 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.
虽然它们两个的对象都是可以被销毁的, 但是由于循环引, 导致垃圾 回收器都不会回收它们, 所以就会导致内存泄露.
三, 垃圾回收
- 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()
python3 结果如下:
- -----0------
- object born,id:0x7fcd059190f0
- object born,id:0x7fcd05919240
- -----1------
- -----2------
- []
- -----3------
- gc: collectable <ClassA 0x7fcd059190f0>
- gc: collectable <ClassA 0x7fcd05919240>
- gc: collectable <dict 0x7fcd05989d48>
- gc: collectable <dict 0x7fcd058f24c8>
- 4
- -----4------
- [<__main__.ClassA object at 0x7fcd059190f0>, <__main__.ClassA object at 0x7fcd05919240>, {'t': <__main__.ClassA object at 0x7fcd05919240>}, {'t': <__main__.ClassA object at 0x7fcd059190f0>}]
- -----5------
- gc: collectable <module 0x7fcd059715e8>
- gc: collectable <dict 0x7fcd0597af08>
- gc: collectable <builtin_function_or_method 0x7fcd0596fdc8>
- ...
说明:
垃圾回收后的对象会放在 gc.garbage 列表
gc.collect()会返回不可达的对象数, 4 等于两个对象以及它们对应的 dict
有三种情况会触发垃圾回收:
调 gc.collect(),
当 gc 模块的计数器达到阀值的时候.
程序退出的时候
四, gc 模块常功能解析
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 的列表.
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())#(590,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), 即检查代对象的垃圾, 并重置计数器
当计数器从 (699,9,0) 增加到(700,9,0),gc 模块就会执 gc.collect(1), 即检查, 代对象的垃圾, 并重置计数器
当计数器从 (699,9,9) 增加到(700,9,9),gc 模块就会执 gc.collect(2), 即检查, , 三对象的垃圾, 并重置计数器
注意点
gc 模块唯处理不了的是循环引的类都有__del__法, 所以项中要避免 定义__del__法
- import gc
- class ClassA():
- pass
- #def __del__(self):
- # print('object born,id:%s'%str(hex(id(self))))
- gc.set_debug(gc.DEBUG_LEAK)
- a = ClassA()
- b = ClassA()
- a.next = b
- b.prev = a
- print("--1--")
- print(gc.collect())
- print("--2--")
- del a
- print("--3--")
- del b
- print("--3-1--")
- print(gc.collect())
- print("--4--")
运行结果如下:
- --1--
- 0
- --2--
- --3--
- --3-1--
gc: collectable <ClassA 0x7f599dc690f0>
- gc: collectable <ClassA 0x7f599dc69160>
- gc: collectable <dict 0x7f599dcdcd48>
- gc: collectable <dict 0x7f599dcdcdc8>
- 4
- --4--
- gc: collectable <module 0x7f599dcc45e8>
- gc: collectable <dict 0x7f599dccdf08>
- gc: collectable <builtin_function_or_method 0x7f599dcc2dc8>
- ...
如果把 del 打开, 运结果为:
- --1--
- 0
- --2--
- --3--
- --3-1--
- gc: collectable <ClassA 0x7fb236853128>
- gc: collectable <ClassA 0x7fb236853160>
- gc: collectable <dict 0x7fb2368c5d48>
- gc: collectable <dict 0x7fb2368c5ec8>
- object born,id:0x7fb236853128
- object born,id:0x7fb236853160
- 4
- --4--
- gc: collectable <module 0x7fb2368ad5e8>
- gc: collectable <dict 0x7fb2368b6f08>
- gc: collectable <builtin_function_or_method 0x7fb2368abdc8>
- ...
来源: https://www.cnblogs.com/ECJTUACM-873284962/p/8964622.html