引用计数
在Python源码中,每一个对象都是一个结构体表示,都有一个计数字段。
typedef struct_object { int ob_refcnt; struct_typeobject *ob_type; } PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是作为引用计数。当一个对象有了新的引用时,它的ob_refcnt就会增加,引用它的对象被删除时则减少。一旦对象的引用计数为0,该对象立即被回收,占用空间就会被释放。
优点
- 简单易用
- 实时性好,一旦没有引用就会被立即释放
缺点
- 需要额外空间去维护引用计数
- 不能解决对象的循环引用
对象的循环引用
循环引用是指两个对象相互引用且没有外部变量引用其中任何一个,导致引用链形成一个环。
>>> a = {} # 对象a的引用计数为1 >>> b = {} # 对象b的引用计数为1 >>> a['b'] = b # b的引用计数增加1 >>> b['a'] = a # a的引用计数增加1 >>> del a # a的引用计数减少1,最后a的引用为1 >>> del b # b的引用计数减少1,最后b的引用为1
在执行完del操作之后,没有任何引用指向a、b对象,但是由于这两个对象各自包含一个对对方的引用,所以引用计数始终保持在1。
按照引用计数中内存回收的原理,由于a和b的计数不为0,所以在使用引用计数法进行内存管理的时候这两个对象不会被回收,它们会一直驻留在内存中,造成内存泄露。
标记清除
标记清除机制主要用于解决循环引用问题。
标记清除算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。主要分为两个阶段:
- 标记阶段,GC会将所有的活动对象打上标记
- 对那些没有打上标记的非活动对象进行回收
区分活动对象与非活动对象
对象之间通过引用即指针连接在一起,构成一个有向图,对象就是这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的对象会被标记为活动对象,不可达的对象就是要被清除的非活动对象。
根对象一般是全局变量、调用栈、寄存器等。
适用范围
标记清除算法作为Python辅助的垃圾收集技术,主要处理的是容器对象,因为对于字符串、数值对象等,不可能造成循环引用的问题,Python会使用一个双向链表将这些容器对象组织起来。
对于标记清除算法来说,有一个比较明显的缺点:为了清除非活动对象,需要扫描整个堆内存,哪怕只剩下小部分活动对象也需要扫描所有对象。
分代回收
分代回收是一种以空间换时间的操作方式,建立在标记清除技术的基础之上,也是Python辅助的垃圾收集技术,主要用于处理容器对象。
Python会将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,主要会被分为3代:年轻代。中年代和老年代,它们会对应3个链表,对应的垃圾收集频率随着对象存活时间的增大而减小。
新创建的对象都会被分配在年轻代,当年轻代链表总数达到上限时,会触发Python的垃圾回收机制,对可回收对象进行回收,而那些不可回收的对象会被移到中年代去。依此类推,老年代对象是存活时间最久的对象,甚至有可能存活在整个系统的生命周期内。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。