一、垃圾回收
1、内存对象池
(1)小整数对象池:
- 小整数对象[-5,257),为常驻内存, 免为整数频繁申请和销毁内存空间。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象。
- 单个字母也共用对象,为常驻内存,不会被垃圾回收。
(2)大整数对象池:
- 每一个大整数,均创建一个新的对象。
- 大整数不共用内存,引用计数为0,销毁
(3)intern机制:
- 所有相同单个单词只占用一个内存空间,靠引用计数去维护何时释放。
- 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
- 含空格的字符串,不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
2、GC垃圾回收
Garbage collection(GC垃圾回收):python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。
GC系统负责三个重要任务:
- 为新生成的对象分配内存
- 识别那些垃圾对象,并且
- 从垃圾对象那回收内存。
(1)引用计数机制:
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject。其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少,当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
- 简单
- 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
- 维护引用计数消耗资源
- 循环引用,所占用的内存永远无法被回收,导致内存泄露,注定python还将引入新的回收机制。(标记清除和分代收集)。
(2)标记-清除:
是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:
- 第一阶段是标记阶段,GC会把所有的『活动对象』打上标记
- 第二阶段是把那些没有标记的对象『非活动对象』进行回收
如何判断哪些对象是活动的呢?
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。
(3)分代回收:
1) Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
2) 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象。
3、gc模块
导致引用计数+1的情况:
- 对象被创建,例如a=23
- 对象被引用,例如b=a
- 对象被作为参数,传入到一个函数中,例如func(a)
- 对象作为一个元素,存储在容器中,例如list1=[a,a]
导致引用计数-1的情况:
- 对象的别名被显式销毁,例如del a
- 对象的别名被赋予新的对象,例如a=24
- 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
- 对象所在的容器被销毁,或从容器中删除对象
(1)查看一个对象的引用计数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33# 查看一个对象的引用计数
import sys
a = 'hello world'
sys.getrefcount(a) # 对象的引用计数比正常计数大1,因为调用函数时传入a,会让a的引用计数+1
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
def f3():
print("-----0------")
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
print("-----1------")
del c1
del c2
print("-----2------")
print(gc.garbage) # 垃圾回收后的对象会放在gc.garbage列表里面
print("-----3------")
print(gc.collect()) # 显式执行垃圾回收,gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict
print("-----4------")
print(gc.garbage)
print("-----5------")
if __name__ == '__main__':
gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
f3()
(2)有三种情况会触发垃圾回收:
- 调用gc.collect(),
- 当gc模块的计数器达到阀值的时候。
- 程序退出的时候
(3)gc模块常用功能解析:1
2
3
4
5gc.set_debug(flags) # 设置gc的debug日志,一般设置为gc.DEBUG_LEAK
gc.collect([generation]) # 显式进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表检查一,二代的对象,2代表检查一,二,三代的对象,如果不传参数,执行一个full collection,也就是等于传2。 返回不可达(unreachable objects)对象的数目
gc.get_threshold() # 获取的gc模块中自动执行垃圾回收的频率。(700,10,10)
gc.set_threshold(threshold0[, threshold1[, threshold2]) # 设置自动执行垃圾回收的频率。
gc.get_count() # 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表.(124,5,3)
(4)gc模块的自动垃圾回收机制:
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取:
1) 例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:
1
2
3
4
5 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)2) 3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。
gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组:
例如(700,10,10) 每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器,例如,假设阀值是(700,10,10):
1) 当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
2) 当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
3) 当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)
注意:gc模块唯一处理不了的是循环引用的类都有del方法,所以项目中要避免定义del方法
持续更新…
最后更新: 2018年12月04日 17:30