一 内存管理:
python中的内存管理机制的层次结构提供了4层,其中个最底层是c语言运行的malloc
和free接口,往上的三层才是由Python实现并且维护的。
第一层则是在第0层的基础之上对其提供的接口进行了统一的封装,这是因为虽然不同的
操作系统都提供标准定义的内存管理接口,但是对于某些特殊的情况不同的操作系统都有
不同的行为,比如说调用malloc(0),有的操作系统会返回NULL,表示内存申请失败;
然而有的操作系统会返回一个貌似正常的指针,但是这个指针所指的内存并不是有效的。
为了广泛的移植性,Python必须保证相同的语义一定代表相同的运行行为。
在第二层主要是对象缓冲池机制,它基于在第二层的内存池。
第三层的内存管理机制上,Python构建了更高抽象的内存管理策略,比如说一些常用对象,
包括整数对象、字符串对象等等。
内存池:
Python为了避免频繁的申请和删除内存所造成系统切换于用户态和核心态的开销,
从而引入了内存池机制,专门用来管理小内存的申请和释放。整个小块内存的内存池可以
视为一个层次结构,其一共分为4层,从下之上分别是block、pool、arena和内存池。
需要说明的是:block、pool和area都是代码中可以找到的实体,而最顶层的内存池只是
一个概念上的东西,表示Python对于整个小块内存分配和释放行为的内存管理机制.
注意,内存大小以256字节为界限,大于则通过malloc进行分配,小于则通过内存池分配。
1)block:最小的内存单元,大小为8的整数倍。有很多种类的block,不同种类的
block都有不同的内存大小,申请内存的时候只需要找到适合自身大小的block即可,
当然申请的内存也是存在一个上限,如果超过这个上限,则退化到使用最底层的malloc
进行申请。
2)pool:一个pool管理着一堆有固定大小的内存块,其大小通常为一个系统内存页的大小。
3)arena:多个pool组合成一个arena。
4)内存池:一个整体的概念。
二 垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅。
问题:引用计数的缺陷是循环引用的问题。
解决方法:通过“标记-清除”解决容器对象可能产生的循环引用的问题。
通过分代回收以空间换取时间进一步提高垃圾回收的效率。
在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。
1.引用计数:
原理:当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用
被销毁时,对象的引用计数减1,当对象的引用计数减少为0时,就意味着对象已经再没有
被使用了,可以将其占用的内存释放掉。
优点:引用计数有一个很大的优点,即实时性,任何内存,一旦没有指向它的引用,就会被
立即回收,而其他的垃圾收集技术必须在某种特殊条件下才能进行无效内存的回收。
缺点:但是它也有弱点,引用计数机制所带来的维护引用计数的额外操作与Python运行中所
进行的内存分配和释放,引用赋值的次数是成正比的,这显然比其它那些垃圾收集技术所带来
的额外操作只是与待回收的内存数量有关的效率要低。同时,引用技术还存在另外一个很大的
问题-循环引用,因为对象之间相互引用,每个对象的引用都不会为0,所以这些对象所占用
的内存始终都不会被释放掉。
2.标记-清除
标记-清除只关注那些可能会产生循环引用的对象,显然,像是PyIntObject、
PyStringObject这些不可变对象是不可能产生循环引用的,因为它们内部不可能持有其它
对象的引用。Python中的循环引用总是发生在container对象之间,也就是能够在内部持有
其它对象的对象,比如list、dict、class等等。这也使得该方法带来的开销只依赖于container对象的的数量.
原理:1. 寻找跟对象(root object)的集合作为垃圾检测动作的起点,跟对象也就是一些
全局引用和函数栈中的引用,这些引用所指向的对象是不可被删除的;2. 从root object集合出发,沿着root object集合中的每一个引用,如果能够到达某个对象,则说明
这个对象是可达的,那么就不会被删除,这个过程就是垃圾检测阶段;3. 当检测阶段结束以后,
所有的对象就分成可达和不可达两部分,所有的可达对象都进行保留,其它的不可达对象所占
用的内存将会被回收,这就是垃圾回收阶段。(底层采用的是链表将这些集合的对象连接在一起)
缺点:标记和清除的过程效率不高。
3.分代回收
原理:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,
Python默认定义了三代对象集合,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是
说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。那么如何来衡量
这个存活时间:通常是利用几次垃圾收集动作来衡量,如果一个对象经过的垃圾收集次数越多,
可以得出:该对象存活时间就越长。
三.gc模块常用功能解析
gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理
内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。
常用函数:
1.gc.set_debug(flags)
设置gc的debug日志,一般设置为gc.DEBUG_LEAK
2.gc.collect([generation])
显式进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表检查一,二代的对象,
2代表检查一,二,三代的对象,如果不传参数,执行一个full collection,也就是等于
传2。返回不可达(unreachable objects)对象的数目.
3.gc.set_threshold(threshold0[, threshold1[, threshold2])
设置自动执行垃圾回收的频率。
4.gc.get_count()
获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
gc模块的自动垃圾回收机制
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查+垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代
中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的
垃圾检查中,该对象存活下来,就会被放到三代中。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放
内存的数目,注意是内存分配,而不是引用计数的增加。3是指距离上一次二代垃圾检查,
一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。
gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,
例如(700,10,10)
每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,
就会执行对应的代数的垃圾检查,然后重置计数器
例如,假设阀值是(700,10,10):
1.当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
2.当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
3.当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)
四.应用
1.项目中避免循环引用
2.引入gc模块,启动gc模块的自动清理循环引用的对象机制
3.由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
4.gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__
方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的
对象的__del__来打破僵局
补充file内存知识
f.flush()
f.close()
本文详细介绍了Python的内存管理机制,包括四层结构,内存池管理小内存,以及对象缓冲池。此外,深入讨论了Python的垃圾回收机制,如引用计数、标记-清除和分代回收,分析了它们的优缺点。最后,提到了gc模块的功能,如解决循环引用问题,以及在项目中的应用和注意事项。
5804

被折叠的 条评论
为什么被折叠?



