Python如何进行内存管理

Python022

Python如何进行内存管理,第1张

Python的内存管理,一般从以下三个方面来说:

1)对象的引用计数机制(四增五减)

2)垃圾回收机制(手动自动,分代回收)

3)内存池机制(大m小p)

1)对象的引用计数机制

要保持追踪内存中的对象,Python使用了引用计数这一简单的技术。sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1

2)垃圾回收机制

吃太多,总会变胖,Python也是这样。当Python中的对象越来越多,它们将占据越来越大的内存。不过你不用太担心Python的体形,它会在适当的时候“减肥”,启动垃圾回收(garbage

collection),将没用的对象清除

从基本原理上,当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了

比如某个新建对象,它被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。

然而,减肥是个昂贵而费力的事情。垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率。如果内存中的对象不多,就没有必要总启动垃圾回收。

所以,Python只会在特定条件下,自动启动垃圾回收。当Python运行时,会记录其中分配对象(object

allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。

我们可以通过gc模块的get_threshold()方法,查看该阈值。

3)内存池机制

Python中有分为大内存和小内存:(256K为界限分大小内存)

1、大内存使用malloc进行分配

2、小内存使用内存池进行分配

python中的内存管理机制都有两套实现,一套是针对小对象,就是大小小于256K时,pymalloc会在内存池中申请内存空间当大于256K时,则会直接执行系统的malloc的行为来申请内存空间。

由于python中万物皆对象,所以python的存储问题是对象的存储问题。实际上,对于每个对象,python会分配一块内存空间去存储它。

那么python是如何进行内存分配,如何进行内存管理,又是如何释放内存的呢?

总结起来有一下几个方面:引用计数,垃圾回收,内存池机制

python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数

1、对象被创建 a= 'abc'

2、对象被引用 b =a

3、对象被其他的对象引用 li = [1,2,a]

4、对象被作为参数传递给函数:foo(x)

1、变量被删除 del a 或者 del b

2、变量引用了其他对象 b = c 或者 a = c

3、变量离开了所在的作用域(函数调用结束) 比如上面的foo(x)函数结束时,x指向的对象引用减1。

4、在其他的引用对象中被删除(移除) li.remove(a)

5、窗口对象本身被销毁:del li,或者窗口对象本身离开了作用域。

即对象p中的属性引用d,而对象d中属性同时来引用p,从而造成仅仅删除p和d对象,也无法释放其内存空间,因为他们依然在被引用。深入解释就是,循环引用后,p和d被引用个数为2,删除p和d对象后,两者被引用个数变为1,并不是0,而python只有在检查到一个对象的被引用个数为0时,才会自动释放其内存,所以这里无法释放p和d的内存空间

垃圾回收机制: ① 引用计数 , ②标记清除 , ③分带回收

引用计数也是一种垃圾收集机制, 而且也是一种最直观, 最简单的垃圾收集技术.当python某个对象的引用计数降为 0 时, 说明没有任何引用指向该对象, 该对象就成为要被回收的垃圾了.(如果出现循环引用的话, 引用计数机制就不再起作用了)

优点:简单实时性,缺点:维护引用计数消耗资源,且无法解决循环引用。

如果两个对象的引用计数都为 1 , 但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的, 也就是说 它们的引用计数虽然表现为非 0 , 但实际上有效的引用计数为 0 ,.所以先将循环引用摘掉, 就会得出这两个对象的有效计数.

标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

为了提高效率,有很多对象,清理了很多次他依然存在,可以认为,这样的对象不需要经常回收,可以把它分到不同的集合,每个集合回收的时间间隔不同。简单的说这就是python的分代回收。

具体来说,python中的垃圾分为1,2,3代,在1代里的对象每次回收都会去清理,当清理后有引用的对象依然存在,此时他会进入2代集合,同理2代集合清理的时候存在的对象会进入3代集合。

每个集合的清理时间如何分配:会先清理1代垃圾,当清理10次一代垃圾后会清理一次2代垃圾,当清理10次2代垃圾后会清理3代垃圾。

在Python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放,当创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。

内存池的概念就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。

Python中有分为大内存和小内存:(256K为界限分大小内存)

大小小于256kb时,pymalloc会在内存池中申请内存空间,当大于256kb,则会直接执行 new/malloc 的行为来申请新的内存空间

在python中 -5到256之间的数据,系统会默认给每个数字分配一个内存区域,其后有赋值时都会指向固定的已分配的内存区域

在运行py程序的时候,解释器会专门分配一块空白的内存,用来存放纯单词字符组成的字符串(数字,字母,下划线)

字符串赋值时,会先去查找要赋值的字符串是否已存在于内存区域,已存在,则指向已存在的内存,不存在,则会在大整数池中分配一块内存存放此字符串

Python采用自动内存管理,即Python会自动进行垃圾回收,不需要像C、C++语言一样需要程序员手动释放内存,手动释放可以做到实时性,但是存在内存泄露、空指针等风险。

Python自动垃圾回收也有自己的优点和缺点:优点:

缺点:

Python的垃圾回收机制采用以引用计数法为主,分代回收为辅的策略。

先聊引用计数法,Python中每个对象都有一个核心的结构体,如下

一个对象被创建时,引用计数值为1,当一个变量引用一个对象时,该对象的引用计数ob_refcnt就加一,当一个变量不再引用一个对象时,该对象的引用计数ob_refcnt就减一,Python判断是否回收一个对象,会将该对象的引用计数值ob_refcnt减一判断结果是否等于0,如果等于0就回收,如果不等于0就不回收,如下:

一个对象在以下三种情况下引用计数会增加:

一个对象在以下三种情况引用计数会减少:

验证案例:

运行结果:

事实上,关于垃圾回收的测试,最好在终端环境下测试,比如整数257,它在PyCharm中用下面的测试代码打印出来的结果是4,而如果在终端环境下打印出来的结果是2。这是因为终端代表的是原始的Python环境,而PyCharm等IDE做了一些特殊处理,在Python原始环境中,整数缓存的范围是在 [-5, 256] 的双闭合区间内,而PyCharm做了特殊处理之后,PyCharm整数缓存的范围变成了 [-5, 无穷大],但我们必须以终端的测试结果为主,因为它代表的是原始的Python环境,并且代码最终也都是要发布到终端运行的。

好,那么回到终端,我们来看两种特殊情况

前面学习过了,整数缓存的范围是在 [-5, 256] 之间,这些整数对象在程序加载完全就已经驻留在内存之中,并且直到程序结束退出才会释放占有的内存,测试案例如下:

如果字符串的内容只由字母、数字、下划线构成,那么它只会创建一个对象驻留在内存中,否则,每创建一次都是一个新的对象。

引用计数法有缺陷,它无法解决循环引用问题,即A对象引用了B对象,B对象又引用了A对象,这种情况下,A、B两个对象都无法通过引用计数法来进行回收,有一种解决方法是程序运行结束退出时进行回收,代码如下:

前面讲过,Python垃圾回收机制的策略是以引用计数法为主,以分代回收为辅。分代回收就是为了解决循环引用问题的。

Python采用分代来管理对象的生命周期:第0代、第1代、第2代,当一个对象被创建时,会被分配到第一代,默认情况下,当第0代的对象达到700个时,就会对处于第0代的对象进行检测和回收,将存在循环引用的对象释放内存,经过垃圾回收后,第0代中存活的对象会被分配为第1代,同样,当第1代的对象个数达到10个时,也会对第1代的对象进行检测和回收,将存在循环引用的对象释放内存,经过垃圾回收后,第1代中存活的对象会被分配为第2代,同样,当第二代的对象个数达到10个时,也会对第2代的对象进行检测和回收,将存在循环引用的对象释放内存。Python就是通过这样一种策略来解决对象之间的循环引用问题的。

测试案例:

运行结果:

如上面的运行结果,当第一代中对象的个数达到699个即将突破临界值700时(在打印699之前就已经回收了,所以看不到698和699)进行了垃圾回收,回收掉了循环引用的对象。

第一代、第二代、第三代分代回收都是有临界值的,这个临界值可以通过调用gc.get_threshold方法查看,如下:

当然,如果对默认临界值不满意,也可以调用gc.set_threshold方法来自定义临界值,如下:

最后,简单列出两个gc的其它方法,了解一下,但禁止在程序代码中使用

以上就是对Python垃圾回收的简单介绍,当然,深入研究肯定不止这些内容,目前,了解到这个程度也足够了。