Python中的垃圾回收机制

2023-11-16

  垃圾回收(Garbage Collection,以下简称GC)是一种自动的内存管理机制,有许多不同的实现算法,Python中的GC,以引用计数为主,标记-清除和分代回收为辅。


1、GC

  在程序中定义了一个变量,就是在内存中开辟了一段相应的空间来存值。由于内存是有限的,所以当程序不再需要使用某个变量的时候,就需要销毁该对象并释放其所占用的内存资源,好重新利用这段空间。在C/C++中,无用变量的内存空间,需要由程序员手动释放,这显然非常繁琐,而且一旦有所疏忽,就可能造成内存泄漏(Memory Leak)。当软件系统比较复杂、变量多的时候,程序员往往就忘记释放内存或者在不该释放的时候释放了内存。
  有了GC,程序员就不需要再手动地去控制内存的释放,这一切可以交由语言本身来自动完成。GC本质上做了三件事情:1).为新生对象分配内存;2).垃圾检测;3).垃圾回收。GC是一门古老的艺术,大概在1960年就有了,发展至今,已经有相当多的算法,如标记-清除、引用计数、标记-压缩、分代回收等。GC的来源可能是由编程语言本身内置(如Java、C#)或是经由外面的库所提供,而非建制于语言内部,例如贝姆垃圾收集器就是一种可支持C/C++语言的自动内存管理工具。
  GC最早起源于LISP语言,目前许多语言如Java、C#、Ruby、Python等都支持GC(值得注意的是,C++自身并不支持GC,读者可参考12)。接下来我们就详细地讲解一下Python中的GC。

2、Python中的GC

  Python中的GC,以引用计数为主,标记-清除和分代回收为辅。

2.1、引用计数(reference counting)

  引用计数,是George E. Collins在1960年发明的,算是最早期的垃圾回收实现方法。
  在Python中,每一个对象的核心就是一个结构体PyObject,它的内部有一个引用计数器ob_refcnt

 typedef struct_object {
 int ob_refcnt;
 struct_typeobject *ob_type;
} PyObject;

当一个对象有新的引用时,对象的引用计数+1;当一个对象的引用被销毁时,对象的引用计数-1;当对象的引用计数减少为0时,就意味着对象已经没有被任何人使用了,可以将其所占用的内存释放。

  导致引用计数+1的情况:1).对象被创建;2).对象被复制;3).对象作为函数参数被传入(引用计数+2);4).对象作为一个元素被存储在容器中.
  导致引用计数-1的情况:1).对象的别名被显式销毁;2).对象的别名被赋予新的对象;3).一个对象离开它的作用域,例如函数执行完毕后,其中的局部变量;4).对象所在的容器被销毁,或从容器中删除对象。

  引用计数机制的优缺点是显而易见的:

优点:

  1. 简单;
  2. 实时性:一旦引用计数为0,立即被回收;内存回收的时间分摊到平时;

缺点:

  1. 需要额外的空间来维护引用计数;
  2. 执行效率低:引用计数机制所带来的维护引用计数的额外操作,与程序运行过程中所进行的内存分配、释放和引用赋值的次数成正比

  除了上面提到的,引用计数机制还有一个致命缺点,即无法解决循环引用的问题。我们用一段代码来做进一步的解释:

a = [1, 2] # 对象[1, 2]的引用计数为1
b = [3, 4] # 对象[3, 4]的引用计数为1
a.append(b) # 对象[3, 4]的引用计数为2
b.append(a) # 对象[1, 2]的引用计数为2
del a # 对象[1, 2]的引用计数为1
del b # 对象[3, 4]的引用计数为1

上面的代码中,对象[1, 2]和[3, 4]已经没有了来自外界的引用,这意味着不会再有人使用它们(无法通过其它变量来引用这两个对象),但是它们彼此之间依然有相互的引用,因此引用计数均为1,也就导致它们的内存永远不能被回收。
  这一点是致命的,它与手动进行内存管理所产生的内存泄漏无异(因此,也有很多语言比如Java并没有采用引用计数来实现GC)。为了弥补引用计数的缺陷,Python中引入了其它的GC机制。

2.2、标记-清除(mark and sweep)

  可以包含其它对象引用的容器对象,如list、set、dict、class、instance,都可能产生循环引用,标记-清除可以解决这个问题。
  标记-清除是一种基于追踪(Tracing)回收技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记,把所有的『活动对象』打上标记,第二阶段是回收,对那些没有标记的『非活动对象』进行回收。那么,如何区分活动对象和非活动对象呢?
  对象之间会通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从root object出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达(unreachable)的对象就是要被清除的非活动对象。所谓root object,就是一些全局变量、调用栈、寄存器,这些对象是不可被删除的。

在这里插入图片描述
在上图中,我们把小黑圈视为root object,从小黑圈出发,对象1可达,那么它将被标记,对象2、3可间接可达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。
  标记-清除的过程实际比上面说的还要复杂一下,具体来讲,首先找到root object集合,然后在内存中建立两条链表,一条链表中维护root object集合,称为root链表,而另外一条链表中维护剩下的对象,称为unreachable链表。在标记的过程中,如果发现unreachable链表中存在被root链表中的对象,直接或间接引用的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。

2.3、分代回收(generation collection)

  分代回收,是一种以空间换时间的回收方式,可以提升GC的效率。
  分代回收思想将对象分在不同的集合中,每个集合称为一个“代”(generation),Python中分为3代,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表。每一代的GC频率是不同的,第0代最高,第1代次之,第2代最低。
  根据弱代假说(即越年轻的对象越容易死掉,而老的对象通常会存活更久),新生的对象被放入第0代,如果该对象在第0代的一次GC中活了下来,那么它就被移动到第1代,类似地,如果某第1代对象在第1代的一次GC中活了下来,它就被移动到第2代。
  那么,什么情况下会触发GC呢?具体地,在Python中,gc.set_threshold(threshold0[,threshold1[,threshold2]])可以设置每一代GC被触发的阈值:从上一次第0代GC后,如果分配对象的个数减去释放对象的个数大于threshold0,那么就会对第0代中的对象进行GC; 从上一次第1代GC后,如果第0代被GC的次数大于threshold1,那么就会对第1代中的对象进行GC;同样,从上一次第2代GC后,如果第1代被GC的次数大于threshold2,那么就会对第2代中的对象进行GC。除此之外,还有两种情况会触发GC,第一种是手动调用gc.collect(),第二种便是程序退出。
  从上面的叙述可以看出,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

2.4、其它

a).
  Python中的gc模块提供了一些接口给开发者设置GC相关的选项,具体使用可参考12
b).
  如果循环引用中,两个对象都定义了__del__方法,gc模块不会销毁这两个不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法(例如,两个对象a和b,如果先销毁a,则在销毁b时,会调用b的__del__方法,该方法中很可能使用了a,这时会造成异常),所以为了安全起见,gc模块会把对象放到gc.garbage中,并把它们称为uncollectable。很明显,这种情况会造成内存泄漏,要解决的话,只能显式调用其中某个对象的__del__方法来打破僵局。
c).
  还有一种情况会造成Python中的内存泄漏,即对象一直被全局变量所引用,而我们知道,全局变量的生命周期是非常长的。

2.5、小结

  写到这里,我们尝试着来小结一下Python中的GC机制:Python中,对于所有对象,引用计数都在起作用,一旦某对象的引用计数为0,它所占用的内存就会被释放;而对于容器对象,由于它们会产生循环引用,这是引用计数所无法解决的,于是Python引入了标记-清除的方式来对它们做GC;最后,为了提升标记-清除的GC效率,Python引入了分代回收的机制,以空间换时间。


参考文献

[1] https://juejin.im/post/5b34b117f265da59a50b2fbe
[2] https://www.cnblogs.com/pinganzi/p/6646742.html
[3] https://blog.csdn.net/bluehawksky/article/details/50295089
[4] https://www.cnblogs.com/Xjng/p/5128269.html
[5] https://toutiao.io/posts/206426/app_preview
[6] https://zhuanlan.zhihu.com/p/31150408
[7] https://blog.csdn.net/yueguanghaidao/article/details/11274737
[8] http://python.jobbole.com/83548/?utm_source=blog.jobbole.com&utm_medium=relatedPosts
[9] http://python.jobbole.com/87843/?utm_source=blog.jobbole.com&utm_medium=relatedPosts
[10] http://www.memorymanagement.org/mmref/recycle.html#tracing-collectors
[11] https://zh.wikipedia.org/wiki/垃圾回收_(計算機科學)
[12] http://blog.jobbole.com/109833/
[13] https://baike.baidu.com/item/GC/66426?fr=aladdin
[14] https://baike.baidu.com/item/内存泄漏/6181425?fromtitle=内存泄露&fromid=305116
[15] https://www.jianshu.com/p/b309f4cb579d
[16] https://blog.csdn.net/yeahhook/article/details/6796242
[17] https://blog.csdn.net/jx232515/article/details/52749551?utm_source=blogxgwz0
[18] https://www.zhihu.com/question/33529443
以上为本文的全部参考文献,对原作者表示感谢。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python中的垃圾回收机制 的相关文章

  • 用于查找列表/集合中唯一元素的代码

    根据上面阴影部分的面积应该代表 A XOR B XOR C XOR A AND B AND C 如何将其翻译成Python代码 代码必须与上述表达式中提供的集合操作密切相关 至少这是首选 该代码必须足够通用 能够处理 3 个以上的列表 UP
  • 如何读取通过追加行不断更新的文件?

    在我的终端中我正在运行 curl user dhelm 12345 https stream twitter com 1 1 statuses sample json gt raw data txt curl 的输出是实时流式 Twitte
  • 向 polls urls.py 添加额外的过滤器会导致测试失败

    按照 djangoproject 的教程 我尝试让 urls py 过滤掉没有选择下面 urlpattern 的民意调查 urlpatterns patterns url r ListView as view queryset Poll o
  • 使用 Python 的 optparse 模块时如何遵守 PEP 257 文档字符串?

    根据PEP 257 http www python org dev peps pep 0257 multi line docstrings命令行脚本的文档字符串应该是它的使用消息 脚本的文档字符串 a 独立程序 应该可用 作为其 使用 消息
  • S3 选择检索 CSV 中的标头

    我尝试使用以下代码从存储在 S 存储桶中的 CSV 中获取记录子集 s3 boto3 client s3 bucket bucket file name file sql stmt SELECT S FROM s3object S LIMI
  • 覆盖现有的 django-admin 命令

    除了编写自定义 django admin 命令之外 这是有详细记录的 https docs djangoproject com en 1 9 howto custom management commands 我希望能够覆盖现有命令 例如ma
  • 来自数据框 groupby 的条形图

    import pandas as pd import numpy as np import matplotlib pyplot as plt df pd read csv arrests csv df df replace np nan 0
  • 如何在返回的 AJAX 调用上使用 django 模板标签?

    我有一个简单的 AJAX 脚本 它在名为的搜索字段中获取输入的字符串AJAXBox并调用一个视图函数 该函数使用过滤器查询数据库并返回与输入参数匹配的所有 User 对象的查询集 当我使用 django 模板标签迭代查询集时 它不起作用 我
  • PyPI 上的轮子平台约束有什么限制吗?

    是否有任何地方 PEP 或其他地方 声明关于 Linux 轮子上传范围的限制 PyPI http pypi io 应该有 具体来说 上传是否被认为是可接受的做法linux x86 64轮子到 PyPI 而不是manylinux1 x86 6
  • 使用 .map() 在 pandas DataFrame 中高效创建附加列

    我正在分析形状与以下示例类似的数据集 我有两种不同类型的数据 abc数据和xyz data abc1 abc2 abc3 xyz1 xyz2 xyz3 0 1 2 2 2 1 2 1 2 1 1 2 1 1 2 2 2 1 2 2 2 3
  • 如何使用 Selenium Webdriver (Python) 在上下文菜单中选择“将图像另存为...”来保存图像

    我正在尝试使用 selenium webdriver 将特定图像保存到目录中 我希望通过模拟右键单击 img 元素并选择 将图像另存为 来实现此目的 使用以下代码我可以打开上下文菜单 但无法选择正确的选项 browser WebDriver
  • django 中的“管理器”是什么?

    我已经阅读了Django官方中的定义文档 https docs djangoproject com en dev topics db managers 我仍然对什么感到困惑Manager does 文档说它们允许您操作数据库表 模型 但我仍
  • Learning_rate 不是合法参数

    我正在尝试通过实现 GridSearchCV 来测试我的模型 但我似乎无法在 GridSearch 中添加学习率和动量作为参数 每当我尝试通过添加这些代码来执行代码时 我都会收到错误 这是我创建的模型 def define model op
  • 让 TensorFlow 在 ARM Mac 上使用 GPU

    我已经安装了TensorFlow在 M1 上 ARM Mac 根据这些说明 https github com apple tensorflow macos issues 153 一切正常 然而 模型训练正在进行CPU 如何将培训切换到GPU
  • 如何让 Python 找到 ffprobe?

    I have ffmpeg and ffprobe安装在我的 mac macOS Sierra 上 并且我已将它们的路径添加到 PATH 中 我可以从终端运行它们 我正在尝试使用ffprobe使用以下代码获取视频文件的宽度和高度 impor
  • Python中的MariaDB连接器无法连接到远程服务器

    我使用与远程 Mariadb 服务器的连接已有几个月了 今天 无法再通过 macOS 上的 python mariadb 模块和 mariadb 连接器建立连接 基本安装如下 brew install mariadb connector c
  • 用户的完整 UNIX 用户名

    想知道您是否知道是否有一种巧妙的方法可以从 shell 获取完整的用户名 示例 如果我的 UNIX 用户名是 froyo 那么我想获取我的全名 在本例中 如系统中注册的那样 froyo Abhishek Pratap Finger 命令可以
  • 在Python中打开网站框架或图像

    所以我对 python 相当熟练 并且经常使用 urllib2 和 Cookies 来实现网站自动化 我刚刚偶然发现了 webbrowser 模块 它可以在默认浏览器中打开一个网址 我想知道是否可以从该 url 中仅选择一个对象并打开它 具
  • 如何有效地计算另一列中每个元素的较大元素的数量?

    我有以下内容df name created utc 0 t1 cqug90j 1430438400 1 t1 cqug90k 1430438400 2 t1 cqug90z 1430438400 3 t1 cqug91c 143043840
  • 将自定义属性添加到 Tk 小部件

    我的主要目标是向小部件添加隐藏标签或字符串之类的内容 以在其上保存简短信息 我想到创建一个新的自定义 Button 类 在本例中我需要按钮 它继承所有旧选项 这是代码 form tkinter import class NButton Bu

随机推荐

  • 【原创】第一个iOS应用程序

    摘要 第一个iOS应用程序 包括获取控件 绑定事件 设置属性等内容 iOS Objective C 目录 第一章 窗口与应用程序 第二章 添加视图 2 1 从nib文件初始化视图 2 2 使用脚本添加视图 第三章 添加子视图 3 1 通过x
  • 制作自己的 Kindle 电子书

    想象以下场景 你刚收到一台新的 Kindle Paperwhite 心中已然响起了轰轰烈烈的 我今年 或这个冬天 一定要阅读 100 本书 结果发现 想看的书 Amazon 上找不到 或者排版很糟糕 如何解决 自己动手做呗 准备工作 我使用
  • UE4 UI实现改键功能

    主要内容 本文主要讲解如何在UI中实现自定义按键的功能类似于游戏中的改键操作 用到的是UE4自带的第三人称案例 因为第三人称自带了小白人和几个按键绑定就不用再手动去设置 实现步骤 1 创建两个UMG用来展示UI效果 1 创建WBP Key
  • C++链表合并

    有l1和l2两个链表 这两个链表降序排列 把l2合并到l1中 并按降序排列 同时清空l2链表 例如l1 9 8 7 6 l2 12 11 10 5 4 3 2 1 合并后l1 12 11 10 9 8 7 6 5 4 3 2 1 l2 in
  • 【Android】利用intent启动浏览器

    文章目录 一 默认浏览器 二 指定浏览器 三 选择浏览器 一 默认浏览器 需要设置Action和Date属性 构造 Uri uri Uri parse https www baidu com Intent intent new Intent
  • SCADE Suite 状态机之变量隐式赋值

    SCADE Suite 状态机之变量隐式赋值 1 变量的隐式赋值 目的 简化模型设计 Last 只要没有显示赋值 便取上一周期的数值 Default 只要没有显示赋值 便取默认设置的数值 优先级更高 设置方法 2 定义变量的Last值 1
  • LeetCode 817:链表组件(计数)

    解法一 常规解法 建图 DFS 时间复杂度O n O n 空间复杂度因为需要存储图 所以是O n 这种方法是通解 对于所有图都适用 Definition for singly linked list struct ListNode int
  • lua元表以及元方法

    知微出凡 lua元表以及元方法 lua中的变量是没有数据类型的 值有类型 类型有八种nil number boolean string function thread userdata以及table Lua 中的每个值都可以有一个 元表 这
  • 62.[GIS基础]笛卡尔坐标系

    文章目录 笛卡尔坐标系 多坐标系 坐标系的嵌套 坐标变换 坐标系转换 转载请注明原始链接 http blog csdn net a464057216 article details 54578069 后续此博客不再更新 欢迎大家搜索关注微信
  • 基于粒子群算法(PSO)优化径向基神经网络(PSO-RBF)的分类预测。matlab代码,优化参数为扩散速度,采用交叉验证。多特征输入单输出的二分类及多分类模型。程序内注释详细,直接替换数据就

    清空环境变量 warning off 关闭报警信息 close all 关闭开启的图窗 clear 清空变量 clc 清空命令行 读取数据 res xlsread 数据集 xlsx 分析数据 num class length unique
  • 抛去容抗角度,从电容充放电角度理解RC低通滤波器

  • 和你一起从零开始写RISC-V处理器(2)

    RISC V加法指令的实现 文章目录 RISC V加法指令的实现 上期回顾 一 正片开始 编写各个模块 pc reg模块 if模块 rom模块 if id模块 id模块 regs模块 id ex模块 ex模块 二 顶层模块搭建 三 测试文件
  • 学懂最小生成树(克鲁斯卡尔算法)

    本节 小编将带大家了解最小生成树的第二种构成算法 克鲁斯卡尔算法 Kruskal algorithm 当然 对另一种算法感兴趣的朋友可以看看之前的这篇文章 学懂最小生成树 普里姆算法 目录 一 实现原理 二 代码实现 一 实现原理 克鲁斯卡
  • 后端开发——Flask框架从入门到入坟(中)

    前言 在上一篇文章中荔枝已经梳理了Flask的基础语法 但是想要靠这些东西来写一个项目是远远不够的噢 我们还需要一个更加清晰的项目逻辑来搭建一个Flask后端项目框架 在真实的项目开发中 我们还需要了解如何搭建数据库 如何管理高效管理代码
  • leetcode刷题——栈与队列

    队列 vs 栈 栈 从头进 从头出 只有头部一个进出口 队列 从尾进 从头处 头和尾分别负责出和进 适用于配对问题 20 有效的括号 运用栈尾进头出的思想实现配对 当我们遇到一个左括号时 我们会期望在后续的遍历中 有一个相同类型的右括号将其
  • js 判断数组是否有元素重复

    这里有一个js数组 判断数组是否有重复元素 具体代码 var vecotr for i 0 i
  • rdkafka线程过多_Kafka快速入门(十一)——RdKafka源码分析

    Kafka快速入门 十一 RdKafka源码分析 一 RdKafka C源码分析 1 Kafka OP队列 RdKafka将与Kafka Broke的交互 内部实现的操作都封装成Operator结构 然后放入OP处理队列里统一处理 Kafk
  • 计算机应用技术图像图形处理,计算机图像处理应用技术论文

    摘要 全息技术是物理学中的重大发现 近年来在各个行业得到广泛的应用 作为全息技术中的两个重要部分 CCD和计算机图像处理技术 在推动数字全息新一轮发展中起到至关重要的作用 本文将着重从计算机应用方面阐述图像处理技术在全息中的应用 关键词 计
  • 【机器学习经典算法】K近邻(KNN):核心与总结

    文章目录 1 初识K近邻 2 相知 2 1 K近邻三要素 2 2 KD树 2 2 1 kd树的构建 2 2 2 kd树的搜索 3 总结 1 初识K近邻 K 近邻 K Nearest Neighbors KNN 可以说是整个机器学习算法中最为
  • Python中的垃圾回收机制

    垃圾回收 Garbage Collection 以下简称GC 是一种自动的内存管理机制 有许多不同的实现算法 Python中的GC 以引用计数为主 标记 清除和分代回收为辅 1 GC 在程序中定义了一个变量 就是在内存中开辟了一段相应的空间