python采用的是引用计数机制为主,分代回收(隔代回收) 和标记-清除两种机制为辅的策略。
引用计数机制
正是因为有引用,对象才会在内存中存在。
引用计数是一种非常高效的内存管理手段,当一个pyhton对象被引用时其引用计数增加1,当其不再被引用时引用计数减1,当引用计数等于0的时候,对象就被删除了。
优点
- 实现简单。
- 垃圾回收的实时性。一旦没有引用,内存被直接释放;分摊处理内存回收的时间。
缺点
- 使用额外内存维护引用计数
- 循环引用导致内存泄漏
导致对象的引用计数+1的情况
- 创建对象
- 对象被引用
- 对象作为参数,传入到函数中
- 对象作为容器的元素,如l = [a, a]
导致对象的引用计数-1的情况
- 引用被重新赋值
- 引用被del,del删除的是引用,而不是对象
- 超过作用域
- 元素所在的容器被销毁
分代回收
从理论上说,对象的创建数目==释放数目。但是如果存在循环引用的话,肯定是创建>释放数量,当创建数与释放数量的差值达到规定的阈值的时候,当当当当~分代回收机制就登场啦。
Python根据对象的存活时间将内存划分为年轻代、中年代和老年代,分别用0,1,2表示。
越年轻的对象越容易死掉,老的对象通常会存活更久。 新生的对象被放入0代,如果该对象在第0代的一次gc垃圾回收中活了下来,那么它就被放到第1代里面(它就升级了)。如果第1代里面的对象在第1代的一次gc垃圾回收中活了下来,它就被放到第2代里面。
分代回收算法:
- 从上一次第0代gc后,如果分配对象的个数减去释放对象的个数大于threshold0,那么就会对第0代中的对象进行gc垃圾回收检查。
- 从上一次第1代gc后,如果第0代被gc垃圾回收的次数大于threshold1,那么就会对第1代中的对象进行gc垃圾回收检查。
- 从上一次第2代gc后,如果第1代被gc垃圾回收的次数大于threshold2,那么就会对第2代中的对象进行gc垃圾回收检查。
gc.set_threshold(threshold0[, threshold1[, threshold2])
设置各代执行垃圾回收的阈值。
有三种情况会触发垃圾回收:
- 调用gc.collect(),显式进行垃圾回收;
- 当gc模块的计数器达到阈值的时候;
- 程序退出的时候。
分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象,因为对于字符串、数值对象是不可能造成循环引用问题。
标记清除技术
标记清除用来解决循环引用产生的问题,循环引用只有在容器对象才会产生,比如字典,元祖,列表等。
在标记清除算法中有两个链表,root链表和unreachable链表。unreachable链表中的对象会被回收。
为什么设置两个链?
unreachable链表中可能存在被root链表中的对象、直接或间接引用的对象,这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。
Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。