GC作为现代编程语言中的一种自动内存管理机制,主要关注两件事:1.在内存中找到无用的垃圾资源2.清除这些垃圾,释放内存给其他对象使用。GC彻底将程序员从资源管理的负担中解放出来,让他们可以将更多的时间花在业务逻辑上。但这并不代表coder不能理解GC。毕竟多了解一些GC知识,对于我们写出更加健壮的代码还是有帮助的。Python语言默认采用的垃圾回收机制是“引用计数”。该算法最早由GeorgeE.Collins于1960年提出。50年后的今天,该算法仍在被许多编程语言使用。“引用计数法”的原理是:每个对象都维护一个ob_ref字段,用来记录该对象当前被引用的次数。每当有新的引用指向该对象时,它的引用计数ob_ref就加1,而每当该对象的引用计数ob_ref减1时,该对象的引用就失效了。一旦该对象的引用计数为0,该对象将立即被回收,该对象占用的内存空间将被释放。它的缺点是需要额外的空间来维护引用计数。这个问题是次要的,但主要的问题是不能解决对象的“循环引用”。所以Java等很多语言都不用这个算法来清除垃圾。征收机制。什么是循环引用?A和B互相引用,A和B都没有外部引用。虽然它们的引用计数都是1,但显然应该被回收。例子:a={}#对象A的引用计数1b={}#对象B的引用计数为1a['b']=b#B的引用计数增加1b['a']=a#A的引用计数增加1dela#A的引用计数减少1,***A对象的引用为1delb#B的引用减1,***B对象的引用为1。本例中,程序执行del后声明中,A和B对象不再有任何指向这两个对象的引用,但是这两个对象中的每一个都包含对另一个对象的引用。虽然这两个对象不能通过其他变量引用这两个对象,这是两个不活跃的对象或者GC的垃圾对象,但是它们的引用计数并没有减少为零。因此,如果使用引用计数的方式来管理这两个对象,它们将不会被回收,会一直驻留在内存中,从而造成内存泄漏(内存空间在使用后没有释放)。为了解决对象的循环引用问题,Python引入了两种GC机制:mark-sweep和generationalcollection。Mark-Sweep“Mark-Sweep”算法是一种基于跟踪收集(tracingGC)技术的垃圾收集算法。分为两个阶段:第一阶段是标记阶段,GC会标记所有“活动对象”,第二阶段是回收那些没有标记的对象“非活动对象”。那么GC是怎么判断哪些是活跃对象,哪些是非活跃对象呢?对象通过引用(指针)连接在一起形成一个有向图,对象构成这个有向图的节点,引用关系构成这个有向图到图的边。从根对象开始,沿着有向边遍历对象。可达对象标记为活动对象,不可达对象为待清除的非活动对象。根对象是全局变量、调用堆栈和寄存器。上图中,我们把小黑圈当做全局变量,也就是根对象。从小黑圈开始,可以直接到达物体1,然后会被标记,可以间接到达物体2和3。mark,而4和5是不可达的,则1、2、3是活跃对象,4和5是非活跃对象,会被GC回收。作为Python的辅助垃圾回收技术,mark-and-sweep算法主要处理一些容器对象,比如list、dict、tuple、instance等,因为不可能对string和numeric对象造成循环引用问题。Python使用双向链表来组织这些容器对象。但是,这种简单粗暴的mark-and-sweep算法也有明显的缺点:在清除非活跃对象之前必须依次扫描整个堆内存,即使只剩下少量活跃对象也要扫描所有对象。分代恢复分代恢复是一种以空间换取时间的操作方式。Python根据对象的存活时间将内存划分为不同的集合。每个集合称为一代。Python将内存分为3个“代”,分别是年轻代(0thgeneration)、中间代(1stgeneration)、老年代(2ndgeneration)。它们对应3个链表,它们的垃圾回收频率随着对象存活时间的增加而降低。新创建的对象会分配到新生代。当年轻代链表总数达到上限时,会触发Python垃圾回收机制,回收那些可以回收的对象,那些不会回收的对象会被移到Go到中年,并且以此类推,老年代的对象是存活时间最长的对象,即使在整个系统的生命周期中也是如此。同时,分代回收基于标记清除技术。分代收集也将这些容器对象作为Python的辅助垃圾收集技术来处理。
