python中的垃圾回收机制怎么实现
python中的垃圾回收机制怎么实现
这篇文章主要讲解了“python中的垃圾回收机制怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python中的垃圾回收机制怎么实现”吧!
python采用的是引用计数机制为主,标记-清除和**分代收集(隔代回收)**两种机制为辅的策略。
计数引用
因为python中一切皆为对象,你所看到的一切变量,本质上都是对象的一个指针。当一个对象不再调用的时候,也就是当这个对象的引用计数(指针数)为 0 的时候,说明这个对象永不可达,自然它也就成为了垃圾,需要被回收。可以简单的理解为没有任何变量再指向它。
importosimportpsutil#显示当前python程序占用的内存大小defshow_memory_info(hint):pid=os.getpid()p=psutil.Process(pid)info=p.memory_full_info()memory=info.uss/1024./1024print({}memoryused:{}MB.format(hint,memory))
可以看到调用函数 func(),在列表 a 被创建之后,内存占用迅速增加到了 433 MB:而在函数调用结束后,内存则返回正常。这是因为,函数内部声明的列表 a 是局部变量,在函数返回后,局部变量的引用会注销掉;此时,列表 a 所指代对象的引用数为 0,Python 便会执行垃圾回收,因此之前占用的大量内存就又回来了。
deffunc():show_memory_info(initial)globalaa=[iforiinrange(10000000)]show_memory_info(afteracreated)func()show_memory_info(finished)##########输出##########initialmemoryused:48.88671875MBafteracreatedmemoryused:433.94921875MBfinishedmemoryused:433.94921875MB
新的这段代码中,global a 表示将 a 声明为全局变量。那么,即使函数返回后,列表的引用依然存在,于是对象就不会被垃圾回收掉,依然占用大量内存。同样,如果我们把生成的列表返回,然后在主程序中接收,那么引用依然存在,垃圾回收就不会被触发,大量内存仍然被占用着:
deffunc():show_memory_info(initial)a=[iforiinderange(10000000)]show_memory_info(afteracreated)returnaa=func()show_memory_info(finished)##########输出##########initialmemoryused:47.96484375MBafteracreatedmemoryused:434.515625MBfinishedmemoryused:434.515625MB
那怎么可以看到变量被引用了多少次呢?通过 sys.getrefcount
importsysa=[]#两次引用,一次来自a,一次来自getrefcountprint(sys.getrefcount(a))deffunc(a):#四次引用,a,python的函数调用栈,函数参数,和getrefcountprint(sys.getrefcount(a))func(a)#两次引用,一次来自a,一次来自getrefcount,函数func调用已经不存在print(sys.getrefcount(a))##########输出##########242
如果其中涉及函数调用,会额外增加两次1. 函数栈2. 函数调用
从这里就可以看到python不再需要像C那种的认为的释放内存,但是python同样给我们提供了手动释放内存的方法 gc.collect()
importgcshow_memory_info(initial)a=[iforiinrange(10000000)]show_memory_info(afteracreated)delagc.collect()show_memory_info(finish)print(a)##########输出##########initialmemoryused:48.1015625MBafteracreatedmemoryused:434.3828125MBfinishmemoryused:48.33203125MB---------------------------------------------------------------------------NameErrorTraceback(mostrecentcalllast)in1112show_memory_info(finish)--->13print(a)NameError:nameaisnotdefined
截止目前,貌似python的垃圾回收机制非常的简单,只要对象引用次数为0,必定为触发gc,那么引用次数为0是否是触发gc的充要条件呢?
循环回收
如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?
deffunc():show_memory_info(initial)a=[iforiinrange(10000000)]b=[iforiinrange(10000000)]show_memory_info(aftera,bcreated)a.append(b)b.append(a)func()show_memory_info(finished)##########输出##########initialmemoryused:47.984375MBaftera,bcreatedmemoryused:822.73828125MBfinishedmemoryused:821.73046875MB
从结果显而易见,它们并没有被回收,但是从程序上来看,当这个函数结束的时候,作为局部变量的a,b就已经从程序意义上不存在了。但是因为它们的互相引用,导致了它们的引用数都不为0。这时要如何规避呢
\1. 从代码逻辑上进行整改,避免这种循环引用
\2. 通过人工回收
importgcdeffunc():show_memory_info(initial)a=[iforiinrange(10000000)]b=[iforiinrange(10000000)]show_memory_info(aftera,bcreated)a.append(b)b.append(a)func()gc.collect()show_memory_info(finished)##########输出##########initialmemoryused:49.51171875MBaftera,bcreatedmemoryused:824.1328125MBfinishedmemoryused:49.98046875MB
python针对循环引用,有它的自动垃圾回收算法1. 标记清除(mark-sweep)算法2. 分代收集(generational)
标记清除
标记清除的步骤总结为如下步骤1. GC会把所有的『活动对象』打上标记2. 把那些没有标记的对象『非活动对象』进行回收那么python如何判断何为非活动对象?通过用图论来理解不可达的概念。对于一个有向图,如果从一个节点出发进行遍历,并标记其经过的所有节点;那么,在遍历结束后,所有没有被标记的节点,我们就称之为不可达节点。显而易见,这些节点的存在是没有任何意义的,自然的,我们就需要对它们进行垃圾回收。但是每次都遍历全图,对于 Python 而言是一种巨大的性能浪费。所以,在 Python 的垃圾回收实现中,mark-sweep 使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类对象,list、dict、tuple,instance,才有可能产生循环引用)。

图中把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。
分代回收
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时(当垃圾回收器中新增对象减去删除对象达到相应的阈值时),Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。事实上,分代回收基于的思想是,新生的对象更有可能被垃圾回收,而存活更久的对象也有更高的概率继续存活。因此,通过这种做法,可以节约不少计算量,从而提高 Python 的性能。所以对于刚刚的问题,引用计数只是触发gc的一个充分非必要条件,循环引用同样也会触发。
调试
可以使用 objgraph来调试程序,因为目前它的官方文档,还没有细读,只能把文档放在这供大家参阅啦~其中两个函数非常有用 1. show_refs() 2. show_backrefs()
感谢各位的阅读,以上就是“python中的垃圾回收机制怎么实现”的内容了,经过本文的学习后,相信大家对python中的垃圾回收机制怎么实现这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是恰卡编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!
推荐阅读
-
python(中无效的十进制怎么解决 python怎么转换进制)
python怎么转换进制?Python执行二进制转换:1.十进制到二进制(bin)首先,让让我们看看如何将十进制转换成二进制。我...
-
python怎么清除完全相同的行(python splte如何分隔有多个相同符号的str)
pythonsplte如何分隔有多个相同符号的str?str你的string内容str_(相同的符号)执行完了以后再在相同符号的...
-
python(编程控制电脑关机 如何控制电脑关机)
如何控制电脑关机?可以在电脑的运行窗口中输入输入公式,给电脑可以设置自动关机。1.按开快捷键winr然后打开运行窗口。2.在运行窗...
-
python中的特殊标识符(python 中 标识符中可以有逗号吗)
python中标识符中可以有逗号吗?在python语言中合法的标识符是字母、数字以及_,所以我合法的标识符中肯定不能有逗号if...
-
python(excel 提取数据写入新表 python导入excel数据找不到工作簿)
python导入excel数据找不到工作簿?我可以导入数据后找不到工作,不是因为他的工作没有被转移。什么软件可提取并合并Exce...
-
python中字典定义的四种方法(python global关键字的用法详解)
pythonglobal关键字的用法详解?global标志实际上是目的是提示python讲解器,说被其修饰的变量是全局变量。这样...
-
python(array用法 python如何对两个数组做差处理)
python如何对两个数组做差处理?Python中的列表中的元素肯定不能真接相加,减。t最佳的位置的是将列表装换成Python中的...
-
python多行注释符号怎么表示
python多行注释符号怎么表示这篇文章主要介绍“python多行...
-
python支持的操作系统是什么
python支持的操作系统是什么这篇文章主要介绍“python支持...
-
python如何判断列表为空
python如何判断列表为空这篇文章主要介绍“python如何判断...