众所周知,我们当代的计算机都是图灵机架构。图灵机架构的本质,就是一条无限长的纸带,对应着我们今天的存储器。在工程学的演化中,逐渐出现了寄存器、易失性存储器(内存)和永久性存储器(硬盘)等产品。其实,这本身来自一个矛盾:速度越快的存储器,单位价格也越昂贵。因此,妥善利用好每一寸高速存储器的空间,永远是系统设计的一个核心。
回到 Python 应用层。
我们知道,Python 程序在运行的时候,需要在内存中开辟出一块空间,用于存放运行时产生的临时变量;计算完成后,再将结果输出到永久性存储器中。如果数据量过大,内存空间管理不善就很容易出现 OOM(out of memory),俗称爆内存,程序可能被操作系统中止。
而对于服务器,这种设计为永不中断的系统来说,内存管理则显得更为重要,不然很容易引发内存泄漏。什么是内存泄漏呢?
- 这里的泄漏,并不是说你的内存出现了信息安全问题,被恶意程序利用了,而是指程序本身没有设计好,导致程序未能释放已不再使用的内存。
- 内存泄漏也不是指你的内存在物理上消失了,而是意味着代码在分配了某段内存后,因为设计错误,失去了对这段内存的控制,从而造成了内存的浪费。
那么,Python 又是怎么解决这些问题的?换句话说,对于不会再用到的内存空间,Python 是通过什么机制来回收这些空间的呢?
计数引用
我们反复提过好几次, Python 中一切皆对象。因此,你所看到的一切变量,本质上都是对象的一个指针。
那么,怎么知道一个对象,是否永远都不能被调用了呢?
我们上节课提到过的,也是非常直观的一个想法,就是当这个对象的引用计数(指针数)为 0 的时候,说明这个对象永不可达,自然它也就成为了垃圾,需要被回收。
我们来看一个例子:
|
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 |
复制代码
通过这个示例,你可以看到,调用函数 func(),在列表 a 被创建之后,内存占用迅速增加到了 433 MB:而在函数调用结束后,内存则返回正常。
这是因为,函数内部声明的列表 a 是局部变量,在函数返回后,局部变量的引用会注销掉;此时,列表 a 所指代对象的引用数为 0,Python 便会执行垃圾回收,因此之前占用的大量内存就又回来了。
明白了这个原理后,我们稍微修改一下代码:
|
def func(): |
|
show_memory_info('initial') |
|
global a |
|
a = [i for i in
|