python 垃圾回收机制

DAY 18. python垃圾回收机制

python GC主要有三种方式

  • 引用计数
  • 标记清除
  • 分代回收

其中,以引用计数为主。

18.1 引用计数(Reference Counting)

《寻梦环游记》中说,人一生会经历两次死亡,一次是肉体死的时候,另一次是最后一个记得你的人也忘了你时,当一个人没有人记得的时候,才算真的死亡。垃圾回收也是这样,当最后一个对象的引用死亡时,这个对象就会变成垃圾对象。

引用计数的原理是在每次创建对象时都添加一个计数器,每当有引用指向这个对象时,该计数器就会加1,当引用结束计数器就会减1,当计数器为0时,该对象就会被回收。

python 中所有对象所共有的数据成员由一个叫做pyobject的结构体来保存

typedef struct _object {/* 宏,仅仅在Debag模式下才不为空 */_PyObject_HEAD_EXTRA/* 定义了一个 Py_ssize_t 类型的 ob_refcnt 用来计数 */Py_ssize_t ob_refcnt;/* 类型 */struct _typeobject *ob_type;
} PyObject;

里面的ob_refcnt就是垃圾回收用到的计数器,而Py_ssize_t是整数

pyobject中保存的是python对象中共有的数据成员,所以python创建的每一个对象都会有该属性。

在python中可以使用from sys import getrefcount来查看引用计数的值,但一般这个值会比期望的ob_refcnt高,应为它会包含临时应用以作为getrefcount的参数

以下情况ob_refcnt加一

  • 创建对象
  • 引用对象
  • 作为参数传递到函数中
  • 作为成员存储在容器中
from sys import getrefcountfoo: int = 1
print(getrefcount(foo))  # 91 应为包含临时引用,所以会比预期的高很多bar: int = foo
print(getrefcount(foo))  # 92 增加了一个foo的引用,所以计数加一List = []
List.append(foo)
print(getrefcount(foo))  # 93 作为成员存储在容器中,计数加一def Foo(*agrs):print(getrefcount(foo))  # 95 作为参数传递给了函数计数加一,实参与形参的赋值使计数加一
Foo(foo)
print(getrefcount(foo))  # 函数生命周期结束,计数减2

以下情况,计数减一:

  • 当该对象的别名被显式销毁时
  • 该对象的别名被赋予新值时
  • 离开作用域时
  • 从容器中删除时
del bar
print(getrefcount(foo))  # 92 对象的别名被显式销毁List.pop()
print(getrefcount(foo))  # 91 从容器中删除foo2: int = foo
foo2 = 2
print(getrefcount(foo))  # 91 别名被赋予新值

当计数被减为0时,该对象就会被回收

class MyList(list):def __del__(self):print('该对象被回收')s = MyList()
s = []  # s是MyList实例对象唯一的引用,s指向别的对象,MyList的这个实例对象就会被立刻回收
print('end')
# 该对象被回收
# end

优点:

  • 实现简单
  • 内存回收及时,只要没有引用立刻回收
  • 高效对象有确定生命周期

缺点:

  • 维护计数器占用资源
  • 无法解决循环引用问题
# 循环引用
class MyList(list):def __del__(self):print('该对象被回收')if __name__ == '__main__':a = MyList()b = MyList()a.append(b)b.append(a)del adel bprint('程序结束')# 程序结束
# 该对象被回收
# 该对象被回收

a和b相互引用,造成a,b的计数始终大于0,这样就无法使用引用计数的方法处理垃圾,针对这种情况,python使用另外一种GC机制——标记清除来回收垃圾。

18.2 标记清除(Mark-Sweep)

标记清除就是为解决循环引用产生的,应为它造成的内存开销较大,所以在不会产生循环引用的对象上是不会使用的。

  • 哪写对象会产生循环引用?
    只有能“引用”别的对象,才会产生循环引用,那些int,string等是不会产生的,只有“容器”,类似list,dict,class之类才可能产生,也只有这类对象才可能使用标记清除机制。

过程:

  • 去环
  • 计数为0的加入生存组,不为零的加入死亡组
  • 生存组中的元素作为root,root的可达节点从死亡组中提出
  • 回收死亡组中的对象

原理:

from sys import getrefcountclass MyList(list):def __del__(self):print('该对象被回收')a = MyList()
b = MyList()
a.append(b)
b.append(a)
print(f'a的引用计数{getrefcount(a)}')
print(f'b的引用计数{getrefcount(b)}')
del a
print(f'del a的引用计数{getrefcount(b[0])}')c = MyList()
d = MyList()
c.append(d)
d.append(c)
print(f'c的引用计数{getrefcount(c)}')
print(f'd的引用计数{getrefcount(d)}')
del c
del dprint('end')

这是一开始a,b 的情况

[外链图片转存失败(img-dNo6yajQ-1566208695428)(image/GC_01.png)]

他们的计数都是2,cd也一样,使用del语句会断开变量ab与MyList()内存之间的联系

[外链图片转存失败(img-evQV8kJR-1566208695434)(image/GC_02.png)]

这个时候就该标记清除上场了,由于a还存在,而a中引用了b,cd相互引用但都通过del显式清除了,所以经过标记清除,ab会被保留,cd会被清除。
标记清除的第一步是“标记”,通过两个容器来实现————生存容器和死亡容器,python首先会检测循环引用,这时会将所有对象的计数复制一个副本以避免破坏真实的引用计数值,然后检查链表中每个相互引用的对象,把这些对象的计数都减一,这一步叫做去环。
上面ab,cd都相互引用,经过del之后,a的计数依旧是2,bcd的计数是1,去环以后a的计数是1,bcd计数为0。

经过去环以后,将所有计数为0的值(bcd)加入死亡容器,不为0的(a)加入生存容器,这时还不能直接清除死亡容器中的对象,需要二审,先遍历生存容器中的对象,把每一个生存容器中的值作为root object,根据该对象的引用关系建立有向图,可达节点就标记为活动对象,不可达节点就为非活动对象(就是查看生存容器中是否引用了死亡容器中的对象,如果有,就把这个对象从死亡容器解救到生存容器)。
这里a引用了死亡容器中的b,所以b会被解救。

最后,死亡容器中的对象会被清除。

  • 什么时候进行标记清除

标记清除并不像引用计数那样是实时的,而是等待占用内存到达GC阈值的时候才会触发

18.3 分代回收

上面说了标记回收通过生存和死亡两个容器来实现,但这只是为了方便理解说的,在真实情况下,标记清除是依赖分代回收计数完成的。

首先,我们在python中创建的每一个对象都会被收纳进一个链表中,python称其为零代(Generation Zero)经过检测循环引用,会按照规则减去有循环引用的节点的计数值,这时候部分节点的计数值大于0,也有部分节点计数值等于0,大于0的节点会被放入二代,等于0的节点经过“白障算法(write barrier)”就是上面说的二审,通过的就会放在零代,不通过的就会被清除释放。一段时间后,使用同样的算法遍历一代链表,计数大于0的放入二代链表,等于0的进行白障算法检测,通过留在一代,否则释放,python中只有这三代链表,根据 “弱代假说”(新生的对象存活时间比较短,年老的对象存活时间一般较长)python GC 会将主要精力放在零代上,而触发回收则是根据GC阈值决定的,GC阈值是被分配对象的计数值与被释放对象的计数值之间的差异,一旦这个差异超过阈值,就会触发零代算法,回收垃圾把剩余对象放在一代,一代也类似,但随着代数增加,阈值会提高(弱代假说),也就是零代的垃圾回收最频繁,一代次之,二代最少。

[外链图片转存失败(img-lzFtarZM-1566208695440)(image/python GC机制.png)]

18.4 总结

  1. GC的工作:
    • 为新创建的对象分配内存
    • 识别垃圾对象
    • 回收垃圾对象的内存
  2. 什么是垃圾:
    • 没有对象引用
    • 只相互引用,孤岛
  3. python GC机制:
    python GC机制由三部分组成:引用计数,标记清除,分代回收,其中引用计数为主。
    • 引用计数:python所有对象的共同属性由pyobject结构体保存,该结构体中有一个int类型的成员ob_refcnt用来实现引用计数。计数为0时对象为垃圾对象,回收内存。
      • 计数加一的情况:创建对象,对象作为函数参数传递,对象作为成员保存到容器中,对象增加了一个引用
      • 计数减一的情况:通过del显式删除对象,引用指向None或别的对象,从容器中弹出,跳出作用域如函数生命结束
      • 优点:实现简单,实时回收内存
      • 缺点:无法解决循环引用问题,开销大
    • 标记清除和分代回收:是为了解决引用计数无法回收相互引用的问题
      • 作用对象:只作用于可能产生相互引用的“容器对象”如list,dict,class
      • 处理过程:创建对象->加入零代链表->到达阈值->检测循环引用->循环引用的节点计数减少->计数大于0的加入一代链表,小于零的->白障->在一代链表中有他的引用->不清理,保留,没有引用,清理释放内存。
      • 弱代假说:新生的对象一般存活时间较短,年老对象存活时间较长

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457560.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

曲线连接线_荷重位移曲线仪操作使用注意事项-荷重位移曲线仪厂家

荷重位移曲线仪广泛适用于各种按键及开关、DOME片、按键、微力弹片、硅胶按键、汽车开关之荷重-行程测定;Windows中英文双语软件,操作简单方便,软件流畅稳定,所有测试资料(测试条件,曲线,数据结果&#xff…

回归分析什么时候取对数_冬蜜什么时候取,冬天取蜂蜜的方法

大家好,我现在分享的是,在冬天是在什么时候取蜜!冬天在我们南方,取蜜时间是十一月到十二月的时候,只要温度达到15度以上,蜂蜜封盖了就可以取蜜了,并且在冬天我们只能取一次,最晚取蜜…

Opencv与dlib联合进行人脸关键点检测与识别

前言 依赖库:opencv 2.4.9 /dlib 19.0/libfacedetection 本篇不记录如何配置,重点在实现上。使用libfacedetection实现人脸区域检测,联合dlib标记人脸特征点,最后使用opencv的FaceRecognizer实现人脸识别。 准备工作 1、配置好Op…

Category 的一些事

来源:伯乐在线 - Tsui YuenHong 链接:http://ios.jobbole.com/90422/ 点击 → 申请加入伯乐在线专栏作者 新增实践部分:偏方 Hook 进某些方法来添加功能 Category – 简介 Category(类别)是 Objective-C 2.0 添加的新特…

商品综合评价排名

店内有很多产品,而且包含但不局限于以下指标:浏览量、访客数、平均停留时长、详情页跳出率、下单转化率、下单支付转化率、支付转化率、下单金额、下单商品件数、下单买家数、支付金额、支付商品件数、加购件数、访客平均价值、收藏人数、客单价、搜索支…

Ajax实现原理详解

Ajax:Asynchronous javascript and xml,实现了客户端与服务器进行数据交流过程。使用技术的好处是:不用页面刷新,并且在等待页面传输数据的同时可以进行其他操作。 这就是异步调用的很好体现。首先得了解什么是异步和同步的概念。…

SpringJDBC解析3-回调函数(update为例)

PreparedStatementCallback作为一个接口,其中只有一个函数doInPrepatedStatement,这个函数是用于调用通用方法execute的时候无法处理的一些个性化处理方法,在update中的函数实现: protected int update(final PreparedStatementCr…

System.InvalidOperationException : 不应有 Response xmlns=''。

xml如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <Response version"2"><datacash_reference>4700203048783633</datacash_reference><information>Failed to identify the card scheme of the supp…

Navicat Premium连接SQL Server

Navicat Premium连接SQL Server 步骤&#xff1a; 激活SQL Server 服务配置SQL Server网络配置连接SQL Server 激活SQLServer服务 直接搜索 计算机管理 点 服务和应用程序&#xff0c; 点 SQL Server配置管理器&#xff0c; 双击第一个SQL Server服务 不出意外的话&#xf…

processon完全装逼指南

一、引言 作为一名IT从业者&#xff0c;不仅要有扎实的知识储备&#xff0c;出色的业务能力&#xff0c;还需要具备一定的软实力。软实力体现在具体事务的处理能力&#xff0c;包括沟通&#xff0c;协作&#xff0c;团队领导&#xff0c;问题的解决方案等&#xff0c;这些能力在…

解决svn log显示no author,no date的方法之一

只要把svnserve.conf中的anon-access read 的read 改为none&#xff0c;也不需要重启svnserve就行 sh-4.1# grep "none" /var/www/html/svn/pro/conf/svnserve.conf ### and "none". The sample settings below are the defaults. anon-access none转载…

解决larave-dompdf中文字体显示问题

0、使用MPDF dompdf个人感觉没有那么好用&#xff0c;最终的生产环境使用的是MPDF&#xff0c;github上有文档说明。如果你坚持使用&#xff0c;下面是解决办法。可以明确的说&#xff0c;中文乱码是可以解决的。 1、安装laravel-dompdf依赖。 Packagist&#xff1a;https://pa…

mfc程序转化为qt_小峰的QT学习笔记

我的专业是输电线路&#xff0c;上个学期&#xff0c;我们开了一门架空线路设计基础的课&#xff0c;当时有一个大作业是计算线路的比载&#xff0c;临界档距&#xff0c;弧垂最低点和安装曲线。恰逢一门结课考试结束&#xff0c;大作业ddl快到&#xff0c;我和另外两个同专业的…

【IDEA 2016】intellij idea tomcat jsp 热部署

刚开始用IDEA&#xff0c;落伍的我&#xff0c;只是觉得IDEA好看。可以换界面。想法如此的low。 真是不太会用啊&#xff0c;弄好了tomcat。程序启动竟然改动一下就要重启&#xff0c;JSP页面也一样。 IDEA可以配置热部署&#xff0c;打开tomcat配置页面&#xff0c;将红框处&a…

设计模式11---组合模式(Composite Pattern)

一、组合模式定义 将对象组合成树形结构以表示“部分-整体”的层次结构&#xff0c;使得用户对单个对象和组合对象的使用具有一致性。Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compos…

python实现Redis订阅发布

Redis 发布订阅 Redis 发布订阅可以用在像消息通知&#xff0c;群聊&#xff0c;定向推送&#xff0c;参数刷新加载等业务场景 发布订阅模型有三个角色&#xff1a; 发布者&#xff08;Publisher&#xff09;订阅者(Subscriber)频道(channel) 每个订阅者可以订阅多个频道&am…

iOS开发UI篇—xib的简单使用

一、简单介绍 xib和storyboard的比较&#xff0c;一个轻量级一个重量级。 共同点&#xff1a; 都用来描述软件界面 都用Interface Builder工具来编辑 不同点: Xib是轻量级的&#xff0c;用来描述局部的UI界面 Storyboard是重量级的&#xff0c;用来描述整个软件的多个界面&…

【云栖计算之旅】线下沙龙第2期精彩预告:Docker在云平台上的最佳实践

Docker是一个开源的应用容器引擎&#xff0c;提供了一种在安全、可重复的环境中自动部署软件的方式&#xff0c;允许开发者将他们的应用和依赖包打包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化。容器完全使用沙箱机制&…

mysql int类型的长度值

整数类型的存储和范围(来自mysql手册) 类型字节最小值最大值(带符号的/无符号的)(带符号的/无符号的)TINYINT1-1281270255SMALLINT2-3276832767065535MEDIUMINT3-83886088388607016777215INT4-2147483648214748364704294967295BIGINT8-92233720368547758089223372036854775807…

龙王我当定了(一个在QQ刷龙王的脚本)

自从学了python&#xff0c;龙王再也没丢过&#xff0c;就是经常被打, QQ 和 TIM 都可以&#xff0c;发送时要把聊天窗口打开。 # 如果import报错&#xff0c;那可以pip下载这几个模块试一试 import win32gui import win32con import win32clipboard as w import random from…