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…

进程调度

1、策略 策略决定调度程序在何时让什么进程运行。调度器的策略往往决定系统的整体印象,并且,还要负责优化使用处理器时间。 1.1 I/o消耗型和处理器消耗型。 进程可以被分为I/O消耗型和处理器消耗型。前者指进程的大部分时间用来提交I/O请求或者等待I/O请…

Django,Ajax,Vue实现文章评论功能

Django评论 评论复杂的地方在于需要实现点击提交评论后评论内容需要立刻出现在下面,还要保持页面位置不变,所以提交后不能整体刷新页面,因为刷新以后页面肯定在最上面,而评论一般都在最下面,所以要用到Ajax 整个过程用…

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

大家好,我现在分享的是,在冬天是在什么时候取蜜!冬天在我们南方,取蜜时间是十一月到十二月的时候,只要温度达到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 添加的新特…

python tfidf特征变换_机器学习的“万能模板” - 数据分析

最后是文本变量。很遗憾Titanic数据集中没有合适的文本变量。一般我们处理文本变量的方法是,合并所有的文本形成一个变量,然后调用Count Vectorizer或者TfidfVectorizer算法,将文本数据转换成数字。大部分情况下,TfidfVectorizer比…

python实现哈希表

# python 实现哈希表class HashTable:"""哈希函数的构造解决冲突"""def __init__(self, source):self.source sourceself._index []self._val []self.table []self._mod 13def Output(self):print(self._index)print(self._val)def _create…

商品综合评价排名

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

ionic资源网站

http://ionichina.com/topic/570b1f4ecd63e4247a7cfcf3 http://doc.ionicmaterialdesign.com/#intro http://ionicmaterial.com/demo/ 10大materialhttp://www.open-open.com/news/view/192f93e转载于:https://www.cnblogs.com/znsongshu/p/6079357.html

pytorch神经网络因素预测_实战:使用PyTorch构建神经网络进行房价预测

微信公号:ilulaoshi / 个人网站:lulaoshi.info本文将学习一下如何使用PyTorch创建一个前馈神经网络(或者叫做多层感知机,Multiple-Layer Perceptron,MLP),文中会使用PyTorch提供的自动求导功能,训练一个神经…

SQL基本操作

SQL 操作 检索数据 SELECT 检索数据 -- 检索单个列 SELECT 列名 FROM table_name;-- 检索多个列 SELECT 列1, 列2 FROM table_name;-- 检索所有列 SELECT * FROM table_name;-- 检索不同的值 SELECT DISTINCT 列名 FROM table_name;限制检索结果 -- SQL Server / Access SE…

git 忽略 部分文件夹_git提交忽略某些文件或文件夹

记得第一次用 github 提交代码,node_modules 目录死活传不上去,哈哈哈,后来才知道在 .gitignore 文件里设置了忽略 node_modules 目录上传。是的, .gitignore 文件就是设置那些你不想用 git 一起上传的文件和文件夹。比如刚接触到…

Ajax实现原理详解

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

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

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

python上下文管理器

DAY 23. python上下文管理器 Python 的 with 语句支持通过上下文管理器所定义的运行时上下文这一概念。 此对象的实现使用了一对专门方法,允许用户自定义类来定义运行时上下文,在语句体被执行前进入该上下文,并在语句执行完毕时退出该上下文&…

勾股定理python思路_趣叮咚编程数学揭秘:为什么勾股定理a+b=c?

我们都知道:三角形3个外角之和360度可是谁知道为什么等于360度呢?其实利用编程制作动图演绎了解啦:那勾股定理abc又是为什么呢?还有很多有趣的数学公式都可以演绎:圆的面积公式、圆周长...通过动图演绎原来晦涩难懂的定…

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…

mysql 单标递归_MySql8 WITH RECURSIVE递归查询父子集的方法

背景开发过程中遇到类似评论的功能是&#xff0c;需要时用查询所有评论的子集。不同数据库中实现方式也不同&#xff0c;本文使用Mysql数据库&#xff0c;版本为8.0Oracle数据库中可使用START [Param] CONNECT BY PRIORMysql 中需要使用 WITH RECURSIVE需求找到name为张三的孩子…