python 内存管理
内存管理
1
2
3
4
5
6
7
8
9* 内存状况
* 引用计数(使用sys包中的getrefcount(param)查看)
* 对象引用对象
* 垃圾回收
* 阈值
gc.set_threshold()
gc.get_threshold()
* 分代回收策略
* 孤立"引用环"问题内存状态
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50在python中,执行"x = 1"后,我们需要了解其过程: 系统先找一块内存,将数字1存储进去,然后将"x"指向当前的这块内存
如下:
>>> x = 1
>>> print(x)
1
>>> id(x)
37577064
示例1:
>>> x = 4 # 说明 x 指向了内存地址 37576992,内存地址中存储值为 4
>>> id(x)
37576992
>>> y = 5 # 说明 y 指向了内存地址 37576968,内存地址中存储值为 5
>>> id(y)
37576968
示例2:
>>> x = y # 说明 x 指向了 y 所指向的内存地址
>>> id(x)
37576968
>>> id(y)
37576968
示例3:
>>> x = 4 # 说明 x 指向了内存地址 37576992,内存地址中存储值为 4
>>> id(x)
37576992
>>> x = 5 # 说明 x 指向了内存地址 37576968,内存地址中存储值为 5
>>> id(x)
37576968
在python中,一开始初始化存储在内存的东西是不可以更改的(x),我们所能更改的只是它的引用
示例4:
>>> x = 'hello'
>>> y = 'hello'
>>> id(x)
139654005921952
>>> id(y)
139654005921952
>>> x = "hello world"
>>> y = "hello world"
>>> id(x)
140026311813376
>>> id(y)
140026311813280
综上:
如果是短字符, x 和 y 指向的是同一个内存引用对象地址,内存地址存储值唯一
如果是长字符, x 和 y 指向的是不同的内存引用对象地址,即使内存地址存储值一样内存引用计数
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
34
35
36
37
38
39
40
41
42
43
44
45
46引用计数记录指向对象引用的个数:当引用对象时,引用计数加1;当移除对象引用时,引用计数减1;当引用计数变为0,则被释放.引用计数无法释放"引用环"的对象
引用计数增加:
* 对象被创建时
* 别名被创建
* 被作为参数传递给函数
* 成为容器对象的一个元素
引用计数减少:
* 一个本地引用离开了其作用范围,比如函数结束
* 对象别名被销毁(del)
* 对象别名被赋值给其他的对象
* 对象被从一个容器对象中移除
* 容器对象本身被销毁
引用计数获取:
>>> from sys import getrefcount
>>> x = 'hello'
>>> print(getrefcount(x))
2
>>> y = x
>>> print(getrefcount(y))
3
>>> z = y
>>> print(getrefcount(z))
4
>>> z = 'world'
>>> print(getrefcount(z))
2
>>> print(getrefcount(y))
3
对象引用对象:
[root@dev test]# cat obj1.py
#!/usr/bin/env python
class from_obj(object):
def __init__(self,to_obj):
self.to_obj = to_obj
b = [1,2,3]
a = from_obj(b)
print(id(a.to_obj))
print(id(b))
[root@dev test]# python obj1.py
139851871660944
139851871660944垃圾回收
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59垃圾回收机制用来回收循环应用的对象
垃圾回收机制会根据内存的分配和释放情况的而被调用:比如分配内存的次数减去释放内存的次数大于某一个阈值的时候
注意: 当内存溢出时,不会自动调用gc,gc更看重的是垃圾对象的个数,而不是大小.
垃圾回收阈值:
>>> import gc
>>> print(gc.get_threshold())
(700, 10, 10)
>>> gc.set_threshold(600,10,10)
>>> print(gc.get_threshold())
(600, 10, 10)
调用gc的策略有两种: 一种是固定时间间隔进行调用;另一种是基于事件的调用
gc垃圾回收方法:
可以发现,只有容器对象才会出现引用循环,比如:列表,字典,类,元组
首先,为了追踪容器对象,需要每个容器对象维护两个额外的指针,用来将容器对象组成一个链表,指针分别指向前后两个容器对象,方便插入和删除操作;其次,每个容器对象还得添加gc_refs字段
一次gc垃圾回收步骤:
1.使得gc_refs等于容器对象的引用计数
2.遍历每个容器对象(a),找到它(a)所引用的其它容器对象(b),将那个容器对象(b)的gc_refs减去1
3.将所有gc_refs大于0的容器对象(a)取出来,组成新的队列,因为这些容器对象被容器对象队列的外部所引用
4.任何被新队列里面的容器对象,所引用的容器对象(旧队列中)也要加入到新队列里面
5.释放旧队列里面的剩下的容器对象
gc分代机制:
gc采用分代(generations)的方法来管理对象,总共分为三代:generation 0,1,2
* 新产生的对象放到第0代里面
* 如果该对象在第0代的一次gc垃圾回收中活了下来,那么它就被放到第1代里面
* 如果第1代里面的对象在第1代的一次gc垃圾回收中活了下来,它就被放到第2代里面
gc.set_threshold(threshold0,threshold1,threshold2)
设置gc每一代垃圾回收所触发的阈值:
从上一次第0代gc后,如果分配对象的个数减去释放对象的个数大于threshold0,那么就会对第0代中的对象进行gc垃圾回收检查.
从上一次第1代gc后,如过第0代被gc垃圾回收的次数大于threshold1,那么就会对第1代中的对象进行gc垃圾回收检查
从上一次第2代gc后,如过第1代被gc垃圾回收的次数大于threshold2,那么就会对第2代中的对象进行gc垃圾回收检查
如果threshold0设置为0,表示关闭分代机制
gc常用函数:
* gc.set_debug(flags)
设置gc的debug日志,一般设置为gc.DEBUG_LEAK
* gc.collect([generation])
显式进行垃圾回收,可以输入参数:0代表只检查第一代的对象;1代表检查一,二代的对象;2代表检查一,二,三代的对象.如果不传参数,执行一个full collection,也就是等于传2
* gc.set_threshold(threshold0[, threshold1[, threshold2])
设置自动执行垃圾回收的频率
例如: (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)
* gc.get_count()
获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
例如:(488,3,0)
488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目(注意:是内存分配,而不是引用计数的增加)
3是指距离上一次二代垃圾检查,一代垃圾检查的次数
0是指距离上一次三代垃圾检查,二代垃圾检查的次数