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 的情况

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

这个时候就该标记清除上场了,由于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阈值是被分配对象的计数值与被释放对象的计数值之间的差异,一旦这个差异超过阈值,就会触发零代算法,回收垃圾把剩余对象放在一代,一代也类似,但随着代数增加,阈值会提高(弱代假说),也就是零代的垃圾回收最频繁,一代次之,二代最少。

18.4 总结

  1. GC的工作:

    • 为新创建的对象分配内存
    • 识别垃圾对象
    • 回收垃圾对象的内存
  2. 什么是垃圾:
    • 没有对象引用
    • 只相互引用,孤岛
  3. python GC机制:
    python GC机制由三部分组成:引用计数,标记清除,分代回收,其中引用计数为主。

    • 引用计数:python所有对象的共同属性由pyobject结构体保存,该结构体中有一个int类型的成员ob_refcnt用来实现引用计数。计数为0时对象为垃圾对象,回收内存。

      • 计数加一的情况:创建对象,对象作为函数参数传递,对象作为成员保存到容器中,对象增加了一个引用
      • 计数减一的情况:通过del显式删除对象,引用指向None或别的对象,从容器中弹出,跳出作用域如函数生命结束
      • 优点:实现简单,实时回收内存
      • 缺点:无法解决循环引用问题,开销大
    • 标记清除和分代回收:是为了解决引用计数无法回收相互引用的问题
      • 作用对象:只作用于可能产生相互引用的“容器对象”如list,dict,class
      • 处理过程:创建对象->加入零代链表->到达阈值->检测循环引用->循环引用的节点计数减少->计数大于0的加入一代链表,小于零的->白障->在一代链表中有他的引用->不清理,保留,没有引用,清理释放内存。
      • 弱代假说:新生的对象一般存活时间较短,年老对象存活时间较长

python 垃圾回收机制相关推荐

  1. python 释放变量所指向的内存_通俗易懂的Python垃圾回收机制及内存管理

    Python垃圾回收机制及内存管理 内存管理: 先定义一个变量 name='wxl' 那么python会在内存中开辟一小块区域存放"wxl",此时变量的值是我们真正想要存储的,wx ...

  2. python是不是特别垃圾-谈谈python垃圾回收机制

    什么是垃圾回收机制? 首先,咱先来解释名词,垃圾回收是不是就是将没用的,废弃的东西回收起来. 在坐的各位都没有女朋友对吧,那难以想象你们的房间会是一个什么样子,可能会有很多垃圾,很凌乱,自己也不收拾. ...

  3. python垃圾回收离职_谈谈python垃圾回收机制

    什么是垃圾回收机制? 首先,咱先来解释名词,垃圾回收是不是就是将没用的,废弃的东西回收起来. 在坐的各位都没有女朋友对吧,那难以想象你们的房间会是一个什么样子,可能会有很多垃圾,很凌乱,自己也不收拾. ...

  4. python垃圾回收机制原理_如何理解和掌握Python垃圾回收机制?

    在编程世界里,当一个对象失去引用或者离开作用域后,它就会被当做垃圾而被自动清除,这就是垃圾回收机制.在现在的高级语言如Python.Java都使用了垃圾回收机制,不过与Java采用的垃圾收集机制不同, ...

  5. python多线程详解 Python 垃圾回收机制

    文章目录 python多线程详解 一.线程介绍 什么是线程 为什么要使用多线程 总结起来,使用多线程编程具有如下几个优点: 二.线程实现 自定义线程 守护线程 主线程等待子线程结束 多线程共享全局变量 ...

  6. python垃圾回收机制

    python垃圾回收机制 现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式.自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄 ...

  7. 趣谈Python垃圾回收机制

    今天唠点啥 上次发文看到有位朋友评论"来了,来了,他来了",哈哈哈哈觉得挺逗.确实,老Amy今天又来啦[此处应该有掌声]~ 我就寻思着,上篇文章车都开稳了,今天要怎么假装" ...

  8. Python垃圾回收机制详解

    一.垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅.引用计数的缺陷是循环引用的问题. 在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存. #e ...

  9. python垃圾回收机制(GC)相关问题

    在使用python中很少遇到内存溢出的问题,也不关心内存的管理问题,这是高级语言自带的处理机制,将内部的垃圾空间清除. 要清楚是怎么回收垃圾的,那我们应该先明白什么情况下产生垃圾,就涉及到python ...

最新文章

  1. 导出excel 数字前少0_【产品介绍】数字压力校验仪
  2. Android NDK编译中在libs\armeabi中加入第三方so库文件的方法
  3. 线程调度、公平锁和非公平锁、乐观锁和悲观锁、锁优化、重入锁
  4. vue 悬浮按钮_Vue@哇!几行代码实现拖拽视图组件
  5. 硬件:如何选购适合自己的显示器
  6. java中 set集合_第8篇 Java中的集合(Set)
  7. 民生证券手机网上开户流程
  8. java socket 双网卡_java获取双网卡ip地址
  9. 七. 多线程编程11.线程的挂起、恢复和终止
  10. 均匀三次b样条曲线_西门子数控曲线加工进给速度优化指令
  11. github上预览Demo网页最简单的方法
  12. 关于:在 Office 2021 中自定义模板
  13. 自由职业者-免费的电子书资源【转载】
  14. Unity中关于IphoneX的屏幕适配
  15. SQL Server2008r2安装
  16. (七)集成学习中-投票法Voting
  17. A Two-step Method for Extrinsic Calibration between a Sparse 3Dand a Thermal Camera 笔记
  18. leetcode解题思路(无代码) 归类汇总版,面试笔试经典例题
  19. Photoshop如何查看各种字体
  20. MSBuild 命令的简单使用

热门文章

  1. VUE: 当前页面 引用自定义公用样式 (:style=“样式名“)
  2. [云框架]KONG API Gateway v1.5 -框架说明、快速部署、插件开发
  3. PYTHON 爬虫笔记十一:Scrapy框架的基本使用
  4. 查询自己OpenGL的版本信息
  5. Opencv与dlib联合进行人脸关键点检测与识别
  6. 数据结构(Java)——查找和排序(1)
  7. #pragma once 与 #ifndef比较分析
  8. STL之deque和其他容器
  9. spdk/dpdk 编译相关问题汇总
  10. 在一个数组中,如何确定所需元素在数组中的位置.