Python的垃圾回收机制

引用计数

引用计数是指当对象的引用计数(指针数)为0时,表示这个对象不可达,需要被回收

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
import os
import psutil

# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)

info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} memory used: {} MB'.format(hint, memory))

def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
show_memory_info('after a created')

func()
show_memory_info('finished')

########## 输出 ##########

initial memory used: 47.19140625 MB
after a created memory used: 433.91015625 MB
finished memory used: 48.109375 MB

这个例子说明的是,a是局部变量,当包含a的函数执行完成之后,a的引用就变为0,也就被回收了,想要改变这种方式的话,只需要将a设置为global变量即可

这里存在的问题是,如果a是被返回的值,那么列表a的生命周期就没有消失,仍然会继续占用内存

1
2
3
4
5
def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
show_memory_info('after a created')
return a

查看对象的引用次数可用sys模块中的getrefcount函数

1
2
3
4
5
a = []
print(sys.getrefcount(a)) #output:2(一次来自于a,一次来自于getrefcount自身)
def get_count(a):
print(sys.getrefcount(a))
#output:4(a,python函数调用,函数参数,getrefcount)

需要注意的是,函数调用时,函数本身和函数参数都会产生调用,sys.getrefcount()并不是统计的指针数,而是统计指针指向的变量数量

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = []
>>> import sys
>>> print(sys.getrefcount(a))
2
>>> b = a
>>> print(sys.getrefcount(a))
3
>>> c = b
>>> d = b
>>> f = c
>>> print(sys.getrefcount(a))
6

而回收内存的方法也很简单,先调用del语句,在强制调用gc.collect()手动启动垃圾回收即可

循环引用

python中绝大部分都采用的引用计数的垃圾回收机制,但是,当多个计数存在相互调用的情况下时,可能为内存带来很大的负担

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
b = [i for i in range(10000000)]
show_memory_info('after a, b created')
a.append(b)
b.append(a)

func()
show_memory_info('finished')
#output
"""
initial memory used: 8.2421875 MB
after a, b created memory used: 783.05078125 MB
finished memory used: 783.05078125 MB
"""

由上例可以看出,当func函数执行完之后,按理说a, b的生命周期已经结束,但是可以看出内存并没有释放。因为他们还有相互引用,导致内存并没有释放。

如果想要回收内存,可以显示调用gc.collect()手动启动垃圾回收

1
2
3
func()
gc.collect()
show_memory_info('finished')

python采用标记清除算法分代收集,来启用针对循环引用的自动垃圾回收。

标记清除大致的意思是,python会想一个有向图一样去遍历每个节点,如果存在没有被标记的节点就会被回收,当然,不可能每次都去遍历全部节点,python采用的是双向链表维护了一个数据结构。

分代收集是指,python将所有对象分为三代。刚刚创建的对象为第0代,经过一次垃圾回收之后,依然存在的对象便会从上一代挪到下一代。而每一代都会有自动垃圾回收的阈值,值是可以单独指定的,当垃圾回收时,到达阈值时,对象就会被回收。

-------------本文结束感谢您的阅读-------------