这篇文章描述的是python最新版本(3.9.0a0)的垃圾回收机制(GC)。
通常,我们不必要担心内存管理,当一个python对象不再引用,python会自动回收内存。但是理解垃圾回收机制会帮助您编写更快更健壮的代码。
内存管理
不像其他的一些编程语言,python不会直接将一些已经释放的内存直接归还给操作系统。相反,python的内存管理机制将会管理这些内存,在将来如果需要分配一些小于512字节的对象时,python将不会在通过操作系统接口申请而是通过之前管理的内存池直接分配。python内存池的大小依赖于使用模式。当python进程结束后将归还所有内存空间。
如果一个长时间运行的python进程持有很大的内存空间,这并不能说明此python进程存在内存泄露,如果您对内存模型感兴趣,您可以阅读本人关于内存模型解读的文章。
垃圾回收算法
官方的cpython的垃圾回收包含两个组件,引用计数收集器和分代垃圾收集器。
引用计数收集器算法比较直接高效,但是存在一个缺点,没法检测到循环引用这种情况。这也是python引入分代垃圾回收机制的主要原因。
在python中引用计数收集器是必须的,而分代垃圾收集器则是可选的且可以手动调用。
引用计数
引用计数收集机制相对比较简单,当对象不在被引用后将会释放内存。
在python中每个变量都指向一个对象,在此对象中包含一些类型信息和实际的值,若申明的变量值跟其它的变量值相同,则只是在同一个对象的引用计数信息加1。
代码示例如下:
![86a18ac4d5d4f53e9fc90753c5d98f26.png](https://i-blog.csdnimg.cn/blog_migrate/a01e90cd928c77030bb2d1864bfec6b1.jpeg)
什么情况下引用计数值会增加呢?
l 对象分配
l 参数引用
l 对象之间的引用
如果引用计数(ob_refcnt)字段值为0时,python将会自动调用对象管理相关的函数释放(注意这里的释放小对象只是释放到内存池)。
![2e087c7be000806ea4570bc395e39f3e.png](https://i-blog.csdnimg.cn/blog_migrate/de62a5549ced1de8548a828624ab2e6f.jpeg)
如果把变量申明在函数、类、代码块外边,则变量存储在全局空间(globals)。通常这些变量的生命周期维护到整个python进程结束,这样就会一种情况,这些变量的引用计数直到生命周期结束也不会变为0。
如果把变量申明在函数、类、代码块里面,则变量存储在局部空间(local)。当python解释器运行完当前区域后,将会销毁局部空间中存储的变量和一些在当前空间中创建的引用关系。
这对理解程序在局部空间中运行方式和状态至关重要,python解释器假定所有申请的变量都在使用。要从内存中删除某些东西,需要向变量分配新值或者从代码块中退出。在python中,最常用的代码块就是定义函数,而在函数中垃圾回收经常发生,这也是要把函数定义的即精炼又简单的原因。
您可以使用sys.getrefcount函数检查对象的引用计数。
下面举个例子:
![519368c99c148f0b7b3f54af70b9c3d4.png](https://i-blog.csdnimg.cn/blog_migrate/3490b6eb4c3b150d5c46a617abf3d765.jpeg)
在上面的例子中,可以看到函数的引用在python进程结束后被销毁。
有些情况下需要永久地移除全局和局部变量,我们可以使用del语法移除一个变量或者引用关系。这种方式在Jupyter notbooks中非常有用因为它的环境中的变量都是全局的。
python使用引用计数机制回收内存的主要原因是历史遗留的。现如今对这种技术的弱点有诸多争论,一些人声称现代垃圾回收算法比引用计数机制高效很多。引用计数回收机制也存在诸多问题,比如循环引用、线程锁、内存和性能开销。
引用计数机制的优点也显而易见,对不在引用的对象立即销毁,简单粗暴。
至此,引用计数机制分析到此,关于分代回收机制及一些通用的调试方法请移步至下一节。