1.引用计数机制
引用计数通过记录对象被引用的次数来管理对象。
对对象的引用都会使得引用计数加1,移除对对象的引用,引用计数则会减1,
当引用计数减为0时,对象所占的内存就会被释放掉。
引用计数可以高效的管理对象的分配和释放,但是有一个缺点,就是无法释放引用循环的对象。
引用次数增加的情况:
1.对象被创建:x=4
2.另外的别人被创建:y=x
3.被作为参数传递给函数:foo(x)
4.作为容器对象的一个元素:a=[1,x,'33']
引用次数减少的情况:
1.一个本地引用离开了它的作用域。比如上面的foo(x)函数结束时,x指向的对象引用减1。
2.对象的别名被显式的销毁:del x ;或者del y
3.对象的一个别名被赋值给其他对象:x=789
4.对象从一个窗口对象中移除:myList.remove(x)
5.窗口对象本身被销毁:del myList,或者窗口对象本身离开了作用域。
2.垃圾回收
基本原理上,当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。比如下面的表:
a = [1, 2, 3]
del a
del a后,已经没有任何引用指向之前建立的[1, 2, 3]这个表。用户不可能通过任何方式接触或者动用这个对象。这个对象如果继续待在内存里,就成了不健康的脂肪。当垃圾回收启动时,Python扫描到这个引用计数为0的对象,就将它所占据的内存清空
垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率。如果内存中的对象不多,就没有必要总启动垃圾回收。所以,Python只会在特定条件下,自动启动垃圾回收。当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。
我们可以通过gc模块的get_threshold()方法,查看该阈值:
import gc
print(gc.get_threshold())
返回(700, 10, 10),后面的两个10是与分代回收相关的阈值,后面可以看到。700即是垃圾回收启动的阈值。可以通过gc中的set_threshold()方法重新设置。
我们也可以手动启动垃圾回收,即使用gc.collect()。
分代回收
Python同时采用了分代(generation)回收的策略。
1.分代回收的基本假设
这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾。我们的程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些“长寿”对象,我们相信它们的用处,所以减少在垃圾回收中扫描它们的频率。
2.如何分代?
Python将所有的对象分为0,1,2三代。所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。
这两个次数即上面get_threshold()返回的(700, 10, 10)返回的两个10。也就是说,每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。
3.调整分代回收
同样可以用set_threshold()来调整,比如对2代对象进行更频繁的扫描。
import gc
gc.set_threshold(700, 10, 5)
引用机制的缺陷:无法释放引用循环的对象
例子:
a = []
b = [a]
a.append(b)
del a
del b
这个时候就会出现引用计数不为0,但是也无法被使用,此时需要利用到垃圾回收机制中循环垃圾回收器,确保释放循环引用对象。
gc垃圾回收方法(寻找引用循环对象):
可以发现,只有容器对象才会出现引用循环,比如列表、字典、类、元组。
首先,为了追踪容器对象,需要每个容器对象维护两个额外的指针,
用来将容器对象组成一个链表,指针分别指向前后两个容器对象,方便插入和删除操作。
其次,每个容器对象还得添加gc_refs字段。
一次gc垃圾回收步骤:
1,使得gc_refs等于容器对象的引用计数。
2,遍历每个容器对象(a),找到它(a)所引用的其它容器对象(b),将那个容器对象(b)的gc_refs减去1。
3,将所有gc_refs大于0的容器对象(a)取出来,组成新的队列,因为这些容器对象被容器对象队列的外部所引用。
4,任何被新队列里面的容器对象,所引用的容器对象(旧队列中)也要加入到新队列里面。
5,释放旧队列里面的剩下的容器对象。(释放容器对象时,它所引用的对象的引用计数也要减1)
3.内存池机制
内存池(memory pool)的概念:
当 创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。内存池的概念就是预先在内存中申请一定数量的,大小相等 的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。
内存池的实现方式有很多,性能和适用范围也不一样。
1,Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
2,Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。
3,对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。