垃圾回收机制有三种,主要采用引用计数机制为主,标记-清除和分代回收机制为辅的策略。
其中,标记-清除机制用来解决计数引用带来的循环引用而无法释放内存的问题,分代回收机制是为提升垃圾回收的效率。
1. 引用计数:
Python中的每个对象都有一个引用计数,每当对象被引用时,其引用计数就会增加;当引用被删除或超出作用域时,引用计数就会减少。当对象的引用计数降为0时,该对象就被认为是不再被需要的,Python的垃圾回收器就会将其内存回收。
然而,引用计数并不能解决循环引用的问题。例如,如果两个对象相互引用,那么即使这两个对象实际上不再被其他部分的代码所使用,它们的引用计数也永远不会降为0,因此无法被垃圾回收器回收。
2. 分代收集:
为了解决循环引用的问题,Python的垃圾回收器还采用了分代收集的策略。Python将所有的对象按照它们的“年龄”分为几代,新创建的对象属于第 0代,当这些对象在一次垃圾回收周期中存活下来时,它们就会被移到下一代。随着对象在内存中存在的时间越来越长,它们会被移到更老的代。
在分代收集中,Python的垃圾回收器会优先回收那些属于较年轻代的对象,因为这些对象更可能是临时性的,不再被需要的。而对于较老的代,垃圾回收器会采用一种更加精细的策略来识别并回收不再被需要的对象。
除此之外,Python还提供了一个可选的循环垃圾回收器,用于检测并回收那些由于循环引用而无法通过引用计数回收的对象。这个循环垃圾回收器会在一定条件下被触发,以确保所有的不再被需要的对象都能被正确地回收。
-
所有的新建对象都是0代对象;
-
当某一代对象经历过垃圾回收,依然存活,就被归入下一代对象。
-
Python将所有的对象分为0,1,2三代。所有的新建对象都是0代对象。
-
垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。
-
当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。
3. 标记-清除(Mark Phase)
- 用来解决引用计数机制产生的循环引用,进而导致内存泄漏的问题 。
(1)标记阶段(Mark Phase):
- 从一组根对象(如全局变量、栈上的局部变量等)开始,递归地访问这些对象引用的所有其他对象。
- 在访问过程中,对每个访问过的对象做一个“标记”,表示它们是可达的,即它们还在被使用。
- 如果某个对象没有被标记,那么它就被认为是不可达的,即垃圾对象。
(2)清除阶段(Sweep Phase):
- 遍历堆中的所有对象。
- 对于那些未被标记的对象(即垃圾对象),回收它们的内存空间。
- 可能还需要对内存进行整理,以便连续分配新的对象。
- 标记-清除算法的优点在于它能够处理循环引用的问题,因为即使两个对象相互引用,只要它们没有被根对象引用,那么在标记阶段它们都不会被标记,从而在清除阶段被回收。
然而,标记-清除算法也有一些缺点:
- 暂停时间:标记和清除过程需要暂停应用程序的执行,这可能导致应用程序的响应性下降。
- 内存碎片:清除阶段后,内存空间可能变得不连续,导致内存碎片问题。这可能会影响后续的内存分配效率。
为了减少暂停时间和内存碎片问题,现代的Python实现(如CPython)可能采用一些优化策略,如增量标记(incremental marking)或分代收集(generational collection)。增量标记允许垃圾回收器在应用程序执行期间分多个小步骤进行标记,从而减少对应用程序的暂停时间。分代收集则是基于对象存活时间的假设,将对象分为不同的代,对不同代的对象采用不同的垃圾回收策略。