简述Python垃圾回收机制「建议收藏」

简述Python垃圾回收机制「建议收藏」引言许多高级语言都具有自己的垃圾回收机制,以管理计算机内存,Python也不例外。对于垃圾回收机制的了解程度,成了开发人员是否真正了解Python的检验手段,在面试的时候许多面试官也喜欢以此作为题目考察面试者Garbagecollection(GC)概述现在的高级语言如java,c#等,都采用了垃圾回收机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由,可…

大家好,又见面了,我是你们的朋友全栈君。

引言

许多高级语言都具有自己的垃圾回收机制,以管理计算机内存,Python也不例外。对于垃圾回收机制的了解程度,成了开发人员是否真正了解Python的检验手段,在面试的时候许多面试官也喜欢以此作为题目考察面试者。


Garbage collection(GC)概述

  • 现在的高级语言如javac#等,都采用了垃圾回收机制,而不再是cc++里用户自己管理维护内存的方式。
  • 自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。
  • 对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配回收内存的问题。
  • Python自带的解释器CPython主要使用了三种垃圾回收机制:是引用计数为主,标记-清除分代回收两种机制为辅的策略

Reference Counting 引用计数

  • 引用计数是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。
  • 引用计数Reference Counting的原理是,每个对象都维护一个引用计数字段,记录这个对象被引用的次数
  • 如果有新的引用指向对象,对象引用计数就加一,引用被销毁时,对象引用计数减一,当用户的引用计数为0时,该内存被释放。

优势:

  • 简单
  • 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时

如果仅仅是引用计数回收机制,会存在以下问题:

  • 需要去维护引用计数,存在执行效率问题
  • 无法解决循环引用问题

循环引用:有一组对象的引用计数不为0,但是这组对象实际上并没有被变量引用,它们之间是相互引用,而且也不会有其他的变量再去引用这组对象,最终导致如果使用引用计数法这些对象占用的内存永远不会被释放,从而导致内存泄露

# 循环引用例子
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
print(list1)
print(list2)

>>>[[[...]]]
>>>[[[...]]]

由此可知python还将引入新的回收机制(标记-清除分代回收),辅助引用计数机制完成内存的管理


图解Python的引用计数

  1. 当我们使用Python创建一个Node的实例对象,则Python立即向操作系统请求内存,同时标识符n1指向实例的内存
    在这里插入图片描述
    在这里插入图片描述
  2. 当我们创建第二个对象的时候,再次向OS请求内存
    在这里插入图片描述
  3. 创建一个对象时,Python总是在对象的C结构体里保存一个整数,称为 引用数。期初,Python将这个值设置为1
    在这里插入图片描述
  4. 值为1说明分别有个一个指针指向或是引用这三个对象。假如我们现在创建一个新的Node实例对象JKL
    在这里插入图片描述
  5. 由于我们改变了n1指向了JKL,不再指向ABC,Python就把ABC引用数置为0了。此刻,Python垃圾回收器立刻挺身而出!每当对象的引用数减为0,Python立即将其释放,把内存还给操作系统
    在这里插入图片描述

参考:Ruby 画说 Ruby 与 Python 垃圾回收


标记-清除

上面我们说到循环引用会导致对象占用的内存永远不会被释放,这里还是以图来形象说明以下循环引用的现象,以及Python的标记-清除机制是如何解决循环引用问题的

Python的循环引用

  1. Node类定义之后创建两个Node实例对象,ABC以及DEF,在图中为左边的矩形框。两个节点的引用计数都被初始化为1,因为有两个引用指向各个Node对象(n1n2)
    在这里插入图片描述

  2. 现在,让我们在Node中定义两个附加的属性,next以及prev。(Python中可以在代码运行的时候动态定义实例变量或对象属性,此处不详细说)设置 n1.next 指向 n2,同时设置 n2.prev 指回 n1

    两个Node使用循环引用的方式构成了一个双端链表。同时请注意到 ABC 以及 DEF 的引用计数值已经增加到了2。这里有两个指针指向了每个Node:首先是 n1 以及 n2,其次就是 next 以及 prev
    在这里插入图片描述

  3. 假定我们的程序不再使用这两个对象了,我们将 n1n2 都设置为None
    在这里插入图片描述

Python的标记-清除原理

如上述情况,在Python中形成来一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用,这时候我们想要回收这部分的内存,但是由于所有的引用计数都是1而不是0,无法进行垃圾回收。

所以Python要将循环引用摘掉,就会得出这两个对象的有效计数,同时还要引入Generational GC(Generational garbage collection)算法:

Python使用一种名为零代(Generation Zero)链表来持续追踪活跃的对象。每次我们创建一个对象或其他值的时候,Python会将其加入零代链表。

  1. 当我们创建ABC节点的时候,Python将其加入零代链表,注意这并不是一个真正的链表,并不能直接在你的代码中访问。
    当我们创建DEF节点的时候,Python将其加入同样的链表。
    在这里插入图片描述
    在这里插入图片描述

  2. Python会循环遍历零代链表上的每个对象,检查链表中每个互相引用的对象,根据规则减掉其引用计数,这一步是检测循环引用
    ABCDEF 节点包含的引用数为1。同时有三个其他的对象同时存在于零代链表中,蓝色的箭头指示了有一些对象正在被零代链表之外的其他对象所引用。
    在这里插入图片描述

  3. 通过识别内部引用,Python能够减少许多零代链表对象的引用计数。在下图的第一行,能够看见ABCDEF的引用计数已经变为零了,这意味着收集器可以释放它们并回收内存空间了
    在这里插入图片描述

参考:代式垃圾回收机制


分代回收

Python中的GC阈值

  • 理想状态下,Python创建了多少个对象,一段时间后就应该回收多少的垃圾。
  • 但是实际上,由于上述的循环引用状态的存在,以及有一些对象被长时间的引用,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长
  • Python中存在一个Garbage collection阈值,当被分配对象的计数值与被释放对象的计数值之间的差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上述的标记-清除机制,释放“浮动的垃圾”。

Python的分代回收

  • 当执行标记-清除后,剩余的对象都是真实被引用的,而这些对象都会被移入一代链表

    所谓一代链表就是零代链表执行标记-清除之后的剩余对象组成的链表
    同样的,二代链表就是一代链表执行标记-清除之后的剩余对象组成的链表

在这里插入图片描述

  • Python采用分代回收的机制,实际上是基于弱代假说(weak generational hypothesis)提出的:这个假说由两个观点构成:年轻的对象通常“死”得也快,老对象则很有可能存活更长的时间

  • 基于此,分代回收的核心行为是:垃圾回收器会更频繁的处理新对象。一个新的对象即是你的程序刚刚创建的,而一个老的对象则是经过了几个时间周期之后仍然存在的对象。Python会在当一个对象从零代移动到一代,或是从一代移动到二代的过程中提升(promote)这个对象。

  • 分代回收的意义在于:通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方,它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才回去处理那些老变量

以上。


参考资料:
https://www.jianshu.com/p/1e375fb40506
http://www.cnblogs.com/pinganzi/p/6646742.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/148361.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 具有指令流水线结构的cpu_流水线结构在CPU中的运用是一种

    具有指令流水线结构的cpu_流水线结构在CPU中的运用是一种为什么小小一个CPU,有那么多周期(Cycle)?程序的性能,是由三个因素相乘来衡量的,“指令数×CPI×时钟周期”。和周期相关的只有一个时钟周期,即CPU主频的倒数。一个CPU的时钟周期可以认为是可以完成一条最简单的计算机指令的时间。那为何构造CPU时,有那么多周期?单指令周期处理器一条CPU指令的执行,由FDE三步组成。这个执行过程,至少需花费一个时钟周期。因为在取指令的时候,我们需要通过时钟周期的信号,来决定计数器的自增。很自然,我们希望能确保让这样一整条指令的执行,在一个时钟周期内完成

    2022年8月14日
    4
  • JAVA+Selenium2—淘宝秒杀购物车脚本

    JAVA+Selenium2—淘宝秒杀购物车脚本JAVA+Selenium—淘宝清空购物车脚本前言准备代码注前言对于自己近期学习的归纳与应用,只是个简单的线性脚本。之前学习了selenium,发现csdn上大都是python+selenium,故参考后改成java+selenium,另外把读取本地时间改成了读取网站时间,更为精确。准备JAVA环境,Selenium-2.46.0jar包,火狐浏览器固定版本(自用的可能是32.0)j…

    2022年5月8日
    124
  • CPU型号后缀字母所代表的含义

    CPU型号后缀字母所代表的含义一、Intel桌面式CPU——只看数字你就输了●X后缀X后缀=至高无上的至尊版  X代表Extreme,中文意思是至尊级,代表同一时代性能最强的CPU。如Corei7-5960X、Corei7-4960X。X代表在同一代中只有一款CPU黄袍加身,地位至高无上。加上没有竞争对手可以望其项背,…

    2022年5月30日
    40
  • SSDP协议_Smb协议

    SSDP协议_Smb协议SSDP就是简单服务发现协议(SimpleServiceDiscoveryProtocol)是一种应用层协议,它是构成通用即插即用(也就是UPnP,UPnP是各种各样的智能设备、无线设备和个人电脑等实现遍布全球的对等网络连接的结构)技术的核心协议之一。    简单服务发现协议提供了在局部网络里面发现设备的机制。控制点(也就是接受服务的客户端)能够直接通过使用简单服务发现协议,根据自己的需要查询…

    2022年10月11日
    3
  • nodejs安装淘宝镜像(配置淘宝镜像)

    强烈推荐30个原生JavaScript的demo,包括canvas时钟特效、自定义视频播放器、搜索栏快速匹配、fetch访问资源、console调试技巧等,先fork后学习,详见点击打开链接,欢迎点赞~~~谢谢,共同进步学习!将npm的注册表源设置为国内的镜像1、国内用户,建议将npm的注册表源设置为国内的镜像,可以大幅提升安装速度2、国内优秀npm镜像推荐及使用:http://rin…

    2022年4月18日
    942
  • IDEA使用maven命令打包「建议收藏」

    IDEA使用maven命令打包「建议收藏」前言现在IDEA是最火的java集成开发环境,经常会用到一些maven命令进行必要的操作(例如打jar包),所以在这里做一些简单的总结1.常用打包命令:mvncleanpackage-DskipTests=true//打可执行jar包mvnclean-DskipTests=truedeploy//打包到相应服务器,供其他引用下载2.常用命令列表:mvn-v//…

    2022年4月30日
    57

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号