Python的内存管理机制:引用计数、垃圾回收、内存池机制
一、引用计数
引用计数(reference count),指的是每个Python对象都有一个计数器,记录着当前有多少个变量指向这个对象。
引用计数增减:将一个对象直接或者间接赋值给一个变量时,对象的计数器会加1;当变量被del删除,或者离开变量所在作用域时,对象的引用计数器会减1。
当计数器归零的时候,代表这个对象再也没有地方可能使用了,因此可以将对象安全的销毁
python中的变量&对象&内存地址:
a、变量,通过变量指针引用对象,变量指针指向具体对象的内存空间,取对象的值。
b、对象,类型已知,每个对象都包含一个头部信息(头部信息:类型标识符和引用计数器)
注意:变量名没有类型,类型属于对象(变量只是引用了对象,变量引用什么类型的对象,变量就是什么类型的)
c、内存地址,id()是python的内置函数,用于返回对象的身份,即对象的内存地址。
通过is进行引用所指判断,is是用来判断两个引用所指的对象是否相同。
sys.getrefcount(),用于查看某个对象的引用计数。注意,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此getrefcount()所得到的结果,会比期望的多1。
循环引用(引用环),就是一个对象直接或者间接引用自己本身,引用链形成一个环.
以下创建了两个表对象,并互相引用对方,构成一个引用环。删除了a,b引用之后,这两个对象不可能再从程序中调用。但是由于引用环的存在,这两个对象的引用计数都没有降到0,不会被垃圾回收。
消灭循环引用方式:手动解除引用&使用weakref
a.使用gc模块中的collect()方法,调用此方法时会执行分代回收机制:gc.collect()
b.weakref是Python提供的标准库,旨在解决循环引用
(1)weakref.ref(object, callback = None) 创建一个对object的弱引用,返回值为weakref对象,callback: 当object被删除的时候,会调用callback函数,
(2)weakref.proxy(object, callback = None) 创建一个代理,返回值是一个weakproxy对象,若object已被删除 抛出异常 ReferenceError: weakly-referenced object no longer exists。
(3)weakref.WeakSet() 当WeakSet中的元素被回收的时候,会自动从WeakSet中删除,括号内无需参数
二、垃圾回收
python中使用标记-清除算法(mark-sweep)和分代(generational)算法来垃圾回收
垃圾回收的时机:当Python的某个对象引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾。
标记-清除算法回收机制
在Python中, 所有可以引用其他对象的对象都被称为容器(container). 因此只有容器之间才可能形成循环引用. Python的垃圾回收机制利用了这个特点来寻找需要被释放的对象. 为了记录下所有的容器对象, Python将每一个容器都链到了一个双向链表中, 之所以使用双向链表是为了方便快速的在容器集合中插入和删除对象. 有了这个维护所有容器对象的双向链表以后, Python在垃圾回收时使用如下步骤:
- 对于每一个容器对象, 设置一个
gc_refs
值, 并将其初始化为该对象的引用计数值. - 对于每一个容器对象, 找到所有其引用的对象, 将被引用对象的
gc_refs
值减1. - 执行完步骤2以后所有
gc_refs
值还大于0的对象,都被非容器对象引用着, 至少存在一个非循环引用. 因此不能释放这些对象, 将他们放入另一个集合. - 在步骤3中不能被释放的对象, 如果他们引用着某个对象, 被引用的对象也是不能被释放的, 因此将这些对象也放入另一个集合中.
- 此时还剩下的对象都是没有引用(gc_refs值为0)的对象. 现在可以释放这些对象
分代算法回收机制
这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾。我们的程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些“长寿”对象,在垃圾回收中减少扫描它们的频率。Python还将所有对象根据’生存时间’分为0,1,2三代,所有新创建的对象都分配为第0代. 当这些对象经过一次垃圾回收仍然存在则会被放入第1代中. 如果第1代中的对象在一次垃圾回收之后仍然存在则被放入第2代.对于不同代的对象Python的回收频率也不一样. 可以通过
gc.set_threshold(threshold0[, threshold1[, threshold2]])
来定义.第0代对象执行垃圾回收时机:新增的对象数量减去删除的对象数量大于threshold0时,会执行一次垃圾回收.
第1代对象执行垃圾回收时机:每当第0代对象被检查的次数超过了threshold1时, 第1代对象就会被执行一次垃圾回收.
第2代对象执行垃圾回收实际:每当第1代对象被检查的次数超过了threshold2时,第2代对象也会执行一次垃圾回收.
threshold0&threshold1&threshold2可通过get_threshold()得到三者的结果,返回值为(700, 10, 10) 即:每10次0代对象进行垃圾回收后,会配合1次1代对象的垃圾回收;而每10次1代对象的垃圾回收,才会有1次的2代对象垃圾回收。也可通过set_threshold()来调整回收次数.700是触发垃圾回收的阈值
触发分代回收的情况:
(1)达到了垃圾回收的阈值,Python虚拟机自动执行
(2)手动调用gc.collect(),启动垃圾回收
(3)Python虚拟机退出的时候
gc模块详解:
gc.enable() 开启gc(默认开启)
gc.disable() 关闭gc
gc.isenabled() 判断gc是否开启
gc.collection() 执行一次垃圾回收,不管gc是否处于开启状态都能使用
gc.set_threshold(t0, t1, t2) 设置垃圾回收阈值;也有禁用gc的效果
gc.get_threshold() 获得当前的垃圾回收阈值
gc.get_objects() 返回所有被垃圾回收器(collector)管理的对象。只要python解释器运行起来,就有大量的对象被collector管理,因此该函数的调用非常耗时!
gc.get_referents(*obj) 返回obj对象直接指向的对象
gc.get_referrers(*obj) 返回所有直接指向obj的对象
gc.set_debug(flags) 设置调试选项
常用的flags组合:
gc.DEBUG_COLLETABLE: 打印可以被垃圾回收器回收的对象
gc.DEBUG_UNCOLLETABLE: 打印无法被垃圾回收器回收的对象,即定义了__del__的对象
gc.DEBUG_SAVEALL:当设置此选项,可以被拉起回收的对象不会被真正销毁(free),而是放到gc.garbage这个列表里面,利于在线上查找问题
三、内存池机制
内存池:Python中有分为大内存和小内存(256K为界限分大小内存)
1、大内存>256K使用malloc进行分配
2、小内存<256K使用内存池进行分配
3、Python的内存池(金字塔),分为4个层次:Block、Pool、Arean
block是最小的内存单元,在python中会将这些block进行填充补齐,默认使用的是8字节的对齐,所以所有的block都一定是8的倍数
pool是对某一组固定大小的block的集合(这个pool里面所有的block都是相同大小), pool的对象中有一个szidex存储着它有哪种block,是一个连续的空间,默认使用一个page的大小4K。
所有的pool的内存地址也一定是4K的倍数。
arena是一组pool的集合(对于其中pool的size类型并没有要求一致)。默认的情况下一个arena的大小是256K
得出:较大的内存结构负责维护处于它集合之下的那些内存结构
内存的管理结构:
arena:arena_object是用来管理arena内存结构的一个结构
包含信息:* address,存储 分配的一个连续的内存空间(用来储存pool)的首地址
* pool_address, 下一个还没被用过的pool
* nfreepools, 还剩下的free的pools;ntotalpools,最多能容纳的pools个数
(由于arena在分配用于存储pool空间的时候,要使得第一个pool的起始空间和page的size对齐,很有可能这个total的pools个数要比size除出来的小1)
* freepools, 下一个可用的pool,也是可用的pool链表的头部
pools:pool_header是管理一个pool的头结构,与arena不同的是,arena管理的pools在地址上是和arena_object分离的,但pool_header是在分配的page size的内存头上的,即:它要占用pool的空间。
包含信息:1. ref,已经分配出去的block个数
2. freeblock,freelist head
3. arenaindex, 所属的arena在arena数组中的位置
4. szindx, 这个pool中的block的size对应的索引值
5. nextoffset,unused block的起始位置
6. maxnextoffset, 最大允许的偏移,用来确定是否已经装满了
block:其实就是一个指针,指向分配出去的一个地址
*对象缓冲池:待补充*