Python的内存管理机制?
Python和其他语言不同的是python有自动垃圾回收机制。
比如java,c,c++等语言需要手动垃圾回收,
自动垃圾回收主要是以引用计数为主,标记清除、分代回收为辅的方式进行内存管理。
引用计数当一个对象的引用被创建或者复制时,对象的引用计数+1;当一个对象的引用被销毁是,对象的引用计数-1,;当对象的引用计数减少到为0的时候,就意味着没有人再去使用它,可以将其释放了。
引用计数机制也有他的缺点和优点:
优点:
实时性,引用为零时自动回收,并将回收的时间分摊到平时。
不像其他语言要特定的时间和时机进行回收。
缺点:
自动回收占用额外的内存。
循环引用问题,内存占满造成宕机。、
循环引用是什么?
循环引用时对象A和对象B互相调用形成循环引用。
这个会导致内存不断被占用,可能会导致宕机。
可能造成循环引用对象的容器?
list set dict class instance
如何解决循环引用问题?
解决循环引用的方法就是Python的标记清除机制和隔代(分代)回收机制。
标记清除:
如果两个对象引用计数都为1,但是仅仅存在他们之间的循环引用,那么这两个对象都会被回收,也就是说,他们的引用计数并非为0,但实际上有效的引用计数为0,所通过标记清除将循环引用摘掉,就会得出这两个有效的计数了。
如果进行标记删除?
通过两个容器来完成:死亡容器、存活容器
标记删除第一步:
对执行删除操作后的每个引用-1,此时c的引用为0,d的引用为0,把它们都放在死亡容器内。把那些引用仍然大于0的放到存活容器内。
标记删除第二步:
遍历存活容器,查看是否有存活的容器引用了死亡容器内的对象,如果有就把该对象(注意是对象,比如0x7f94bb602f80,不是对象的引用)从死亡容器内取出放到存活容器内。由于c,b没有对象引用他们经过这一个步骤他们还在死亡组(取出来了只是,有被存活容器内引用过的对象,没有被引用过的独享还在死亡容器内)。
标记清除第三步:
将死亡组所有对象删除,这样就完成对c,d的删除。
分代回收:
刚才提到自动垃圾回收的缺点就是会占用额外的内存,如果出现循环引用的问题,python不断的去自动回收(占用内存)最后可能导致宕机,引出了分代回收。
【比如当某个内存块在经过三次垃圾收集的清洗之后还在存活我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去,当垃圾收集开始工作的时候,大多数情况只对集合B进行垃圾回收,而对集合A垃圾回收要隔上一段时间去进行,也就减少垃圾回收时候所占用的内存消耗,效率自然就提高了。这个过程中集合B的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收因为分代回收而被延迟。】
调优方法:
手动垃圾回收
程序启动时候通过代码进行手动垃圾回收,而减轻自动回收的压力。
del语句,这个语句操作某个对象时候,并不是直接将对象的在内存中删除,而是将该对象的引用计数-1.
当存在村换引用的时候,执行del函数,对应的del方法将不会执行。
Python的自动垃圾回收机制也是引用计数-1,del也是-1,所以del并不会起到有效的作用。
这时候使用垃圾回收接口-GC,来回收和显示这些不能删除的对象。
对Python的垃圾回收进行调优的一个最简单的手段便是关闭自动回收,根据情况手动触发,例如在用Python开发游戏的时候,可以再一局游戏的开始关闭GC,然后在该局游戏结束后手动调用一次GC清理内存。这样就能避免游戏过程中GC造成的卡顿,但是缺点是在游戏过程中,可能因为内存溢出造成的游戏崩溃。
调高垃圾回收的阈值
相比完全手动的垃圾回收,一个更温和的方法是调高垃圾回收的阈值。例如一个游戏可能在某一个时刻产生大量的子弹对象(假如2000个)。而此时Python的垃圾回收的threshold0为1000,。则不会触发垃圾回收,若干秒后,这些子弹命中目标被删除,内存引用计数机制自动释放,一次(可能很耗时的)垃圾回收被完全避免了。
调高阈值的方法还能在一定程度上避免内存溢出的问题(但不能完全避免),同时可能减少客观的垃圾回收开销,根据具体项目的不同,甚至是程序输入的不同,合适的阈值也不同,因此需要反复调试找到一个合适的阈值,这也是一个调高阈值这种手段的一个缺点。
避免循环引用
- 从代码的角度出发避免循环引用的问题。
一个可能更好的方法使用良好的编程习惯尽可能的避免循环引用,两种常见的方法包括:
手动解循环引用使用弱引用。
手动解循环引用
手动解循环引用指在编写代码时写好解开循环引用的diamante,在一个对象使用结束不需要调用时。
使用弱引用
弱引用值当引用一个对象时,不增加该对象的引用计数,当需要用到该对象的时候,需要首先检查该对象是否存在,弱引用的实现方式有很多种,Python自带一个弱引用库weakref。
除了使用Python自带的weakref库以外,通常我们可以根据自己项目的业务逻辑实现弱引用,例如在游戏里开发的时候,通常很多UI想都是有其唯一的ID的,在引用一个对象时我们可以保存其ID而不是直接引用该独享,在需要使用该对象的时候根据ID去检查对象是否存在。
内存管理机制
Python的内存管理内存分为四层:
其中layer2为内存池,layer3为对象缓冲池。Python的对象缓冲池layer3是建立在layer2基础上的。
内存池:
Python的内存机制呈现金字塔形状:
- -1,-2层主要有操作系统进行操作;
- 第0层是C中的malloc,free等内存分配和释放函数进行操作;
- 第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现,当对象小于256K时有该层直接分配内存;
- 第3层是最上层,也就是我们对Python对象的直接操作;