第二张图中我们看到link2、link3、link4的gc_ref都已经为0,当python垃圾回收器再次扫描所有对象时,那么它们就会被标记为GC_TENTATIVELY_UNREACHABLE,同时被移到Unreachable列表中。有同学可能会疑惑为啥link2没有被移到Unreachable列表中,其实它理论上也应该被移到Unreachable列表中,如第三张图所示:
如果python垃圾回收器再次扫描对象时,发现某个对象的ref_count不为0,那么就会将其标记为GC_REACHABLE,表示还正在被引用着,如下图所示的link1就是这种情况。
除了将link1标记为可达的之外,python垃圾回收器,还会从当前可达节点依次遍历所有可达的节点,比如从link1可以到达link2和link3,但link3已经被放到Unreachable列表中,因此还需要将link3再移回到Object to Scan列表中,表示对象还是可以触达的。最终的结果如下图所示,只有link4会被回收掉:
标记-清除法虽然可以解决循环引用的问题,但是缺点也比较明显,就是需要python垃圾回收器对python对象执行两遍扫描,而每次扫描,python解释器就会暂停处理其他事情,等到扫描结束后才能恢复正常。这个过程就好比:图书管理员要对图书馆进行清洁整理,那么将会关闭图书馆,直到收拾干净后才能重新打开图书馆,供同学们使用。
分代垃圾回收那既然在python垃圾回收过程中,会暂停整个应用程序,有没有更好的优化方案呢?答案是肯定的。在python解释器中,对象的存活时间是不一样的:
长时间存活(或一直存活)的对象,它们是内存垃圾的可能性低,可以减少对它们扫描的次数。
临时或短时间存活的对象,这种对象比较容易成为内存垃圾,所以得频繁扫描。
位于前两种情况的之间的对象。可根据情况进行内存扫描。
这样区分对象后,就可以节省每次扫描的时间(不需要所有对象都扫描),重而能提升垃圾回收的速度。
python中结合着上面列出的三种类型的对象分了三个对象代(0,1,2),它们其实对应了3个链表:每一个新生对象在generation zero中,如果它在一轮gc扫描中活了下来,那么它将被移至generation one,在这一个对象代扫描次数将会减少;如果它又活过了一轮gc,它又将被移至generation two,在这一个对象代对象扫描次数将会更少。
python触发垃圾回收扫码的时机python解释器只会在触发某个条件时,才会去执行垃圾回收。这个条件就是当python分配对象的次数和取消分配对象的次数(引用计数变为0)做差值高于某个阈值,我们可以通过python提供的方法来查看这个阈值。
def threshold_gc(): # 获取阈值 print(gc.get_threshold()) # 可设置阈值 gc.set_threshold(800, 10, 10) print(gc.get_threshold()) # 运行结果 (700, 10, 10) (800, 10, 10)上面程序运行结果中值的含义如下:
700是垃圾回收启动的阈值。
后面两个10与分代回收有关(上面介绍过python分了三个对象代:0、1、2),第一个10表示每进行10次0代对象扫描,则进行1次1代对象扫描。
最后一个10表示每进行10次1代对象扫描,则执行1次2代对象扫描。
此外可以自己根据情况,调用set_threshold()方法来调整垃圾回收的频率。比如:set_threshold(700,10,5),相当于增加了对2代对象的扫描频率。
gc这个库中还有一些很好玩的函数,大家可以了解下(更多方法可以参考官方文档):
def gc_method(): # 启动垃圾回收 gc.enable() # 停用垃圾回收 gc.disable() # 手动指定垃圾回收,参数可以指定垃圾回收的代数,不填写参数就是完全的垃圾回收 gc.collect() # 设置垃圾回收的标志,多用于内存泄漏的检测 gc.set_debug(gc.DEBUG_LEAK) # 返回一个对象的引用列表 gc.get_referrers() 额外补充-python内存分层结构在python中,内存管理机制被抽象成分层次的结构,从python解释器Cpython的源码obmallic.c的注释中抓取了对内存分层的描述:
/* Object-specific allocators _____ ______ ______ ________ [ int ] [ dict ] [ list ] ... [ string ] Python core | +3 | <----- Object-specific memory -----> | <-- Non-object memory --> | _______________________________ | | [ Python's object allocator ] | | +2 | ####### Object memory ####### | <------ Internal buffers ------> | ______________________________________________________________ | [ Python's raw memory allocator (PyMem_ API) ] | +1 | <----- Python memory (under PyMem manager's control) ------> | | __________________________________________________________________ [ Underlying general-purpose allocator (ex: C library malloc) ] 0 | <------ Virtual memory allocated for the python process -------> | ========================================================================= _______________________________________________________________________ [ OS-specific Virtual Memory Manager (VMM) ] -1 | <--- Kernel dynamic storage allocation & management (page-based) ---> | __________________________________ __________________________________ [ ] [ ] -2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> | */第-2层是物理内存层。
第-1层是操作系统虚拟的内存管理器。
第0层是C中的malloc、free等内存分配和释放相关的层。当申请的内存大于256K时,会调用第0层的malloc分配内存。