Python的内存管理机制
内存管理机制:引用计数、垃圾回收、内存池。
1. 简述
- 引用计数:引用计数是对变量引用次数的一种标记手段,对象被引用时,我们对其计数增加 1,当该对象不被引用时,我们对其计数减去 1,如果计数变成了 0,说明该对象没有被引用,此时我们就可以删除该对象。
- 垃圾回收:Python 中有三种垃圾回收机制,其中引用计数为主,还有标记清除和分代回收。
- 内存池:
- Python 的内存分为大内存和小内存,分界点是 256K
- malloc 分配大内存,内存池分配小内存
- Python 的内存池是一个金字塔模型:最顶层是用户对对象的直接操作,中间是分配小内存,最底层是大内存。
2.垃圾回收
2.1引用计数是垃圾回收的主要机制
python中的一大特色是: 万物皆对象,在python中每一个对象的核心就是一个结构体PyObject,它的内部有一个引用计数器(ob_refcnt)
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
一个对象被创建出来的时候,因为被New方法引用了,所以他的引用计数就是1,如果它被引用,就会在引用计数上加1,如果引用它的对象被删除,那么他的引用计数就会被减1。当引用计数变为0就会被垃圾回收机制回收。
引用计数的优点就是简单、实时性强,缺点就是维护性高,不能解决如下循环引用的情况
a=[1,2]
b=[3,4]
a.append(b)
b.append(a)
DEL a
DEL b
这种问题出现在可以循环的结构中List Dict Object等等,如上代码a、b间的引用都为1,而a、b被引用的对象删除后都各自减去1(所以他们各自的引用计数还是1),一直是1不会变化。这样的情况单单靠引用计数就无法解决了。 也就引入了下面的方法 标记-清除
2.2标记-清除
标记清除就是用来解决循环引用的问题的。
只有容器对象才会出现引用循环,比如列表、字典、类、元组。 首先为了追踪容器对象,需要每个容器对象维护两个额外的指针, 用来将容器对象组成一个链表,指针分别指向前后两个容器对象,方便插入和删除操作。试想一下,现在有两种情况:
A:
a=[1,3]
b=[2,4]
a.append(b)
b.append(a)
del a
del b
B:
a=[1,3]
b=[2,4]
a.append(b)
b.append(a)
del a
在标记-清除算法中,有两个集中营,一个是root链表(root object),另外一个是unreachable链表。
-
对于情景A,原来再未执行DEL语句的时候,a,b的引用计数都为2(init+append=2),但是在DEL执行完以后,a,b引用次数互相减1。a,b陷入循环引用的圈子中,然后标记-清除算法开始出来做事,找到其中一端a,开始拆这个a,b的引用环(我们从A出发,因为它有一个对B的引用,则将B的引用计数减1;然后顺着引用达到B,因为B有一个对A的引用,同样将A的引用减1,这样,就完成了循环引用对象间环摘除。),去掉以后发现,a,b循环引用变为了0,所以a,b就被处理到unreachable链表中直接被做掉。
-
对于情景B,简单一看那b取环后引用计数还为1,但是a取环,就为0了。这个时候a已经进入unreachable链表中,已经被判为死刑了,但是这个时候,root链表中有b。如果a被做掉,那世界上还有什么正义... ,在root链表中的b会被进行引用检测引用了a,如果a被做掉了,那么b就...凉凉,一审完事,二审a无罪,所以被拉到了root链表中。
QA: 为什么要搞这两个链表?
之所以要剖成两个链表,是基于这样的一种考虑:现在的unreachable可能存在被root链表中的对象,直接或间接引用的对象,这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。
分代回收:
了解分代回收,首先要了解一下,GC的阈值。随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,创建==释放数量应该是这样子。但是如果存在循环引用的话,肯定是创建>释放数量,当创建数与释放数量的差值达到规定的阈值的时候,就需要用到分代回收。
垃圾回收=垃圾检测+释放。
分代回收思想将对象分为三代(generation 0,1,2),0代表幼年对象,1代表青年对象,2代表老年对象。根据弱代假说(越年轻的对象越容易死掉,老的对象通常会存活更久。) 新生的对象被放入0代,如果该对象在第0代的一次gc垃圾回收中活了下来,那么它就被放到第1代里面(它就升级了)。如果第1代里面的对象在第1代的一次gc垃圾回收中活了下来,它就被放到第2代里面。gc.set_threshold(threshold0[,threshold1[,threshold2]])设置gc每一代垃圾回收所触发的阈值。从上一次第0代gc后,如果分配对象的个数减去释放对象的个数大于threshold0,那么就会对第0代中的对象进行gc垃圾回收检查。 从上一次第1代gc后,如过第0代被gc垃圾回收的次数大于threshold1,那么就会对第1代中的对象进行gc垃圾回收检查。同样,从上一次第2代gc后,如过第1代被gc垃圾回收的次数大于threshold2,那么就会对第2代中的对象进行gc垃圾回收检查。
[参考链接-掘金-暴躁的热心网友皮皮文]

浙公网安备 33010602011771号