大家都熟悉C和Java的垃圾回收机制,可以帮助机器很好的去释放内存空间,提升内存的使用效率。那么对于胶水语言Python来说,是怎么样做到垃圾回收的呢?
前言:
对于python来说,一切皆为对象,所有的变量赋值都遵循着对象引用机制。程序在运行的时候,需要在内存中开辟出一块空间,用于存放运行时产生的临时变量;计算完成后,再将结果输出到永久性存储器中。如果数据量过大,内存空间管理不善就很容易出现OOM(out of memory)的情况,俗称爆内存,程序可能被操作系统终止。
而对于服务器,内存管理则显得更为重要,不然很容易引发内存泄露。
这里说的泄露,并不是说我们的内存出了信息安全问题,被恶意程序利用了,而是指程序本身没有设计好,导致程序未能释放已经不再使用的内存。
计数引用
因为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:4.94140625MB
after a created memory used:197.484375MB
finished memory used:5.2421875MB
从输出结果,我们可以看到在调用函数func()之后,在列表a被创建之后,内存迅速占用到了197M;而在函数运行结束之后,内存则返回正常。这是因为,函数内部声明的列表a是局部变量,在函数返回后,局部变量的引用会被注销掉;此时,列表a所指代对象的引用数为0,python便会执行垃圾回收,因此之前占用的大量内存又回来了。
接下来,让我们再来看另外一段代码:
import os
import psutil
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('inital')
global a
a = [i for i in range(10000000)]
show_memory_info('after a created!')
func()
show_memory_info('finished!')
################输出结果################
inital memory used:4.8671875MB
after a created! memory used:197.4375MB
finished! memory used:197.4375MB
从这段代码中,我们可以看到 global a表示将a声明为全局变量。那么,即使函数返回后,列表的引用依然存在,对象并没有因为函数调用结束而被回收掉,依然占用着分配给它的大量内存。那么,我们再来往下思考,如果我们把变量返回到主程序中接受,结果会怎么样呢? 再来看一段代码:
def func():
show_memory_info('initail')
a = [i for i in range(10000000)]
show_memory_info('after a created:')
return a
a = func()
show_memory_info('finished!')
###################output#################
inital memory used:4.8671875MB
after a created! memory used:197.41015625MB
finished! memory used:197.41015625MB
从输出结果可以看出,但我们选择把局部变量的值引用到主程序的时候,在程序执行结束之后,内存的使用情况和作为全局变量完全一致。说明,这种方法依然会造成内存得不到释放。
那么我们怎么看到变量被引用了多少次呢?让我们来看下面一段代码
import sys
a = []
print(sys.getrefcount(a))
def func(a):
print(sys.getrefcount(a))
func(a)
print(sys.getrefcount(a))
#############output#############
2
4
2
其中涉及到函数调用的时候,会额为增加两次
1.函数栈
2.函数调用
从这里可以看到python并不需要我们像C语言那样的释放内存,但是python同样给我们提供了手动释放内错的方法:gc.collect()
看下面一段代码:
import sys
import gc
import os
import psutil
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))
show_memory_info('initial')
a = [i for i in range(10000000)]
del a
gc.collect()
show_memory_info('finished')
print(a)
###############output ####################
initial memory used:4.94140625MB
finished memory used:4.98828125MB
Traceback (most recent call last):
File "varible.py", line 20, in <module>
print(a)
NameError: name 'a' is not defined
截止目前,貌似python的垃圾回收机制非常的简单,只要对象引用次数为0,必定为触发gc,那么引用次数为0是否是触发gc的充要条件呢?