python的内存管理机制

简单说,

Python语言底层使用C语言实现,所以内存分配其实封装了C 的 malloc lib

在上面又封装了一层成为py的allocator 为了增加py的可移植性.

在底层,

1 Block是一个大小固定的内存块。size = 8, 16, 32, 64。。。。

2 一组Block的集合就是一个pool。大小通常是系统的一个内存页(4KB)   Pool----- 是Python操作内存的最基本的操作单元

3 多个pool的聚合就是一个arena, 其中包含了64个pool,有未使用和使用两种状态

 

 当然,内存管理机制不能再像c一样手动,

Python采用引用计数与垃圾回收机制

像所有的内存机制一样需要解决最后一个问题:循环引用问题

清除与分代收集(为了打破循环引用 而引进的补充技术)

 标记清除 (Mark -- Sweep)与 三色标记模型 

任何垃圾回收机制,都是两步走:垃圾检测、垃圾回收。

具体过程:

首先,循环引用只会出现在 container对象,即是内部可持有对其他对象的应用的对象,比如list 、dict、class、instance 像PyIntObject、PyStringObject之间不可能发生循环引用

所以,为了跟踪这些container对象,python用一个双向链表,所有container对象创建后,都会被插入到这个可收集对象链表。

如此,循环引用的对象一定就在这个可收集对象链表中了。

我们必须承认一个事实,如果两个对象的引用计数都为1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非0,但实际上有效的引用计数为0。我们必须先将循环引用摘掉,那么这两个对象的有效计数就现身了。假设两个对象为A、B,我们从A出发,因为它有一个对B的引用,则将B的引用计数减1;然后顺着引用达到B,因为B有一个对A的引用,同样将A的引用减1,这样,就完成了循环引用对象间环摘除。
但是这样就有一个问题,假设对象A有一个对象引用C,而C没有引用A,如果将C计数引用减1,而最后A并没有被回收,显然,我们错误的将C的引用计数减1,这将导致在未来的某个时刻出现一个对C的悬空引用。这就要求我们必须在A没有被删除的情况下复原C的引用计数,如果采用这样的方案,那么维护引用计数的复杂度将成倍增加。

原理:“标记-清除”采用了更好的做法,我们并不改动真实的引用计数,而是将集合中对象的引用计数复制一份副本,改动该对象引用的副本。对于副本做任何的改动,都不会影响到对象生命周期的维护。

这个计数副本的唯一作用是寻找root object集合(该集合中的对象是不能被回收的)。当成功寻找到root object集合之后,首先将现在的内存链表一分为二,一条链表中维护root object集合,成为root链表,而另外一条链表中维护剩下的对象,成为unreachable链表。之所以要剖成两个链表,是基于这样的一种考虑:现在的unreachable可能存在被root链表中的对象,直接或间接引用的对象,这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。

 

分代垃圾收集

 当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少。

当需要回收的内存块越少时,垃圾检测的操作就比垃圾回收操作少。

基于统计规律,采用空间换时间的策略。而这种策略正式当前支撑着Java的关键技术 --- 分代垃圾收集


总体思想:将系统中的内存块根据存活时间划分为不同的集合,每一个集合就称为一个代,垃圾收集的频率随着“代”的存活时间的增大而减小。

也就是说,活的越长的对象,越不像是垃圾。如果对象经历了多次垃圾收集策略的检测还是没有被干掉,那么说明这个对象生存周期很长。

Python中有3个代,维护了3条链表。

 

posted on 2017-09-17 22:23  暴力的轮胎  阅读(626)  评论(0编辑  收藏  举报

导航