python垃圾回收 (GC) 机制

2023-11-17

Python 能够自动进行内存分配和释放,但了解 python 垃圾回收 (garbage collection, GC) 的工作原理可以帮助你写出更好更快的 Python 程序。Python 使用两种算法进行垃圾回收,分别是引用计数 (Reference Counting)分代回收 (Generational garbage collection)

引用计数

引用计数,简而言之就是如果没有变量引用某一对象,那么该对象将会被回收。Python 中的每个变量都是对对象的引用,而不是对象本身。例如,赋值语句只是给右侧对象或右侧变量所对应的对象建立一个引用;一个对象都可以有许多引用。

核心概念:变量是指向一个对象的指针;有n个变量指向某一个对象,那该对象的引用计数则为n,又称该对象有n个引用

import sys
a = [1, 2, 3]
b = a # 赋值操作本身不会对数据进行复制,仅仅是建立引用关系

print(id(a), id(b))  # 4483101696 4483101696,变量a b的id相同,说明a b指向同一对象
print(sys.getrefcount(a))  # 3, 其中调用getrefcount函数会使引用+1

为了跟踪每个对象的引用次数,每个对象都有一个名为引用计数的额外属性,当创建或删除指向对象的指针时,该属性的值会相应的增加或减少。以下三种情况会使对象的引用次数增加:

  • 赋值运算
  • 参数传递
  • 将对象附加到容器对象中

  如果某对象引用计数属性的值为零,CPython 会自动调用该对象特定的内存释放函数。如果该对象还包含对其他对象的引用,那么所包含的其他对象的引用计数也会自动减少。因此,可以依次释放其他对象。

  值得注意的是,在函数、类和代码块(如if-else代码块)之外声明的变量称为全局变量(global variables)。通常,这些变量会一直存在直到 Python 进程结束。因此,全局变量引用的对象的引用计数永远不会下降到零。在python进程中,所有全局变量都存储在一个字典中,可以通过调用globals()函数来获取全局变量。那反过来呢?在代码块内(例如,在函数或类中)定义的变量则具有一个局部作用域,可以通过调用locals()函数来获取局部变量。当 Python 解释器执行完一个代码块时,它会破坏在块内创建的局部变量及其引用。

我们举个例子:

import sys

foo = []
print(sys.getrefcount(foo)) # 2 references, 1 from the foo var and 1 from getrefcount


def bar(a):
    print(sys.getrefcount(a))


bar(foo) # 4 references, from the foo var, function argument, getrefcount and Python's function stack
print(sys.getrefcount(foo)) # 2 references, the function scope is destroyed

当你想删除全局或局部变量时,可以使用删除变量及其引用(而不是对象本身)的 del语句。这在 jupyter notebook中工作时通常很有用,因为在jupyter notebook中所有单元格变量都是全局变量。CPython 使用引用计数的主要原因是历史原因,现在有很多关于这种技术的弱点的争论。比如,有人认为现代的垃圾回收算法可以更高效,无需使用引用计数。引用计数算法存在很多问题,例如循环引用、线程锁定以及额外内存和性能开销。必须指出的是,引用计数是 Python 无法摆脱全局解释锁 (GIL) 的原因之一。

分代回收

上面讲到了引用计数的缺点包括循环引用、线程锁定以及额外内存和性能开销。线程锁定,所以在python中使用多线程;额外内存和性能开销,我们也认了;但是循环引用的问题不解决的话,就会造成内存泄露问题。因此,python引入了分代回收的算法专门来解决循环引用的问题。

引用计数算法非常有效和直接,但它无法检测循环引用,所以python在引用计数的基础上,还需要分代回收。引用计数是 Python必需的功能,不能禁用;而分代回收是可选的,可以手动设置。

在这里插入图片描述

如上图示例所示,lst对象指向自身,而且Object 1Object 2 相互引用。在这两种情况下,这些对象的引用数永远至少为1。我们可以用代码来演示一下:

import gc
import sys
import ctypes

# 通过内存地址去访问没有引用的对象(unreachable objects)
class PyObject(ctypes.Structure):
    _fields_ = [("refcnt", ctypes.c_long)]


gc.disable()  # 禁用分代回收算法
lst = []
lst.append(lst)
lst_address = id(lst)
del lst

object_1, object_2 = {}, {}

object_1['obj2'] = object_2
object_2['obj1'] = object_1
obj_address = id(object_1)
del object_1, object_2

# 手动对象回收
# gc.collect()

# 获取对象引用数量
print(PyObject.from_address(obj_address).refcnt)
print(PyObject.from_address(lst_address).refcnt)

# 或者通过以下方式获取引用数量
# tmp = PyObject.from_address(obj_address)
# print(sys.getrefcount(tmp))

# # output
1
1
2

在上面的示例中,del语句删除了对我们对象的引用(引用计数减 1)。 Python 执行 del语句后,我们的对象不再可以从 Python 代码访问。但是,这些对象仍然存在于内存中。发生这种情况是因为它们仍在相互引用,并且每个对象的引用计数为 1。因为我们前面通过gc.disable()禁用了分代回收,因为循环引用对象无法释放;这时,我们可以通过调用gc.collect()手动触发对象回收。

  我们知道,在python中对象分为可变对象和不可变对象。不可变对象包括int, float, complex, strings, bytes, tuple, rangefrozenset;可变对象包括list, dict, bytearrayset。循环引用仅存在于container对象(比如,list, dict, classes, tuples),python垃圾回收算法主要追踪可变对象及不可变对象tuple。如果tuple, dict包含的元素都是不可变对象,那么回收算法可以不对该对象进行追踪。

垃圾回收的触发

不同于引用计数,循环引用的垃圾回收不是实时作用的,而是定期运行。垃圾回收器将container对象分成三代(0, 1, 2),每个新对象都从第一代开始。如果一个对象在一个垃圾回收轮次中幸存下来,它将移至较旧(更高)的一代。较低代的回收频率高于较高代,因为大多数新对象往往会被先销毁。这样分代回收的策略能提高性能并减少垃圾回收带来的暂停时间。

  为了决定何时进行一轮垃圾回收,每一代都有一个单独的计数器和阈值。计数器存储自上次收集以来的对象分配数减去释放数的差值。每次分配新的容器对象时,CPython 都会检查第0代的计数器是否超过阈值(通过gc.get_count()获得三代对象计数器存储的数值)。如果超过阈值,Python 将触发垃圾回收。我们可以通过gc.get_threshold()gc.set_threshold()查看、设置阈值:

import gc
gc.get_threshold()  # (700, 10, 10) 分别对应三代计数器的阈值
gc.set_threshold(threshold0=800, threshold0=10, threshold0=10)  # 当threshold0设置为0时,禁用循环GC

  在写程序时,可以通过将调试标志设置为gc.DEBUG_SAVEALL,从而将所有unreachable对象添加到gc.garbage 中,帮助提升程序质量:

import gc

gc.set_debug(gc.DEBUG_SAVEALL)

print(gc.get_count())
lst = []
lst.append(lst)
list_id = id(lst)
del lst
gc.collect()
for item in gc.garbage:
    print(item)

参考

python documentation: GC

Garbage collection in Python: things you need to know

stacloverflow reference count

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

python垃圾回收 (GC) 机制 的相关文章

  • 如何把父母和孩子联系起来?

    有两个简单的类 一个只有parent属性 并且两者兼而有之parent and children属性 这意味着同时具备两者的人parent and children继承自唯一的parent 这是只有parent属性 我们就这样称呼它吧Chi
  • DynamodB:如何更新排序键?

    该表有两个键 filename 分区键 和eventTime 排序键 我要更新eventTime对于某些filename Tried put item and update item 发送相同的filename与新的eventTime但这些
  • Python 不考虑 distutils.cfg

    我已经尝试了给出的所有内容 并且所有教程都指向相同的方向 即使用 mingw 作为 python 而不是 Visual C 中的编译器 我确实有 Visual C 和 mingw 当我想使用 pip 安装时 问题开始出现 它总是给Unabl
  • Python3将模块从文件夹导入到另一个文件夹

    我的结构字典是 mainFolder folder1 init py file1 py file2 py folder2 init py file3 py file4 py setup py init py 我需要将 file4 py 从f
  • Python speedtest.net,或等效的[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 是否有一个 Python 库可以实现 SpeedTest net 测试或等效的互联网连接速度测试 GitHub上有一个项目叫速度检查 https gi
  • NSUserNotificationCenter.defaultUserNotificationCenter() 使用 PyInstaller 返回 None

    我正在尝试将通知发送到通知中心 Mac OSX 我正在使用 PyObjC 绑定来使用我们的 python 应用程序中的 cocoa api 我正在使用以下代码片段 import Foundation import objc NSUserNo
  • 使用 scikit 时 scipy.sparse 矩阵的缩放问题

    在使用 scikit learn 解决机器学习问题时 我需要在使用 SVM 进行训练之前对 scipy sparse 矩阵进行缩放 但在文档 http scikit learn org stable modules preprocessin
  • 获取 Keras model.summary() 作为表

    我在 Keras 中创建了相当大的模型 我正在用 LaTeX 写一篇关于它的文章 为了很好地描述 LaTeX 中的 keras 模型 我想用它创建一个 LaTeX 表 我可以手动实现它 但我想知道是否有任何 更好 的方法来实现这一点 我四处
  • 在 Python 中从 Excel 复制 YEARFRAC() 函数

    因此 我使用 python 来自动执行一些必须在 Excel 中执行的重复任务 我需要做的计算之一需要使用yearfrac 这在Python中被复制了吗 I found this https lists oasis open org arc
  • 列表推导式和 for 循环中的 Lambda 表达式[重复]

    这个问题在这里已经有答案了 我想要一个 lambda 列表 作为一些繁重计算的缓存 并注意到这一点 gt gt gt j for j in lambda i for i in range 10 9 9 9 9 9 9 9 9 9 9 Alt
  • 使用会话在 Django 中将文件从一个视图传递到另一个视图

    我当前的工作项目要求我允许用户上传各种格式的文件 目前仅处理 CSV 格式 然后使用包含的数据来绘制图表Pandas http pandas pydata org 图书馆 我决定将图形渲染到模板的最简单方法是为图形创建特定视图 然后将图像从
  • Python在没有pandas的情况下解码excel表

    我正在尝试在 python 中读取 excel 文件而不使用pandas or xlrd 我一直在尝试将结果转换为bytes to utf 8没有任何成功 xls 文件中的数据 colA colB colC spc 1D0 20190705
  • Snakemake:将多个输入用于具有多个子组的一个输出的规则

    我有一个工作管道 用于下载 比对和对公共测序数据执行变体调用 问题是它目前只能在每个样本的基础上工作 i e作为每个单独测序实验的样本 如果我想对一组实验 例如样本的生物和 或技术复制 执行变体调用 则它不起作用 我试图解决它 但我无法让它
  • Eclipse/PyDev 中未使用导入警告,尽管已使用

    我正在我的文件中导入一个绘图包 如下所示 import matplotlib pyplot as plt 稍后我会在我的代码中成功使用此导入 fig plt figure figsize 16 10 然而 Eclipse 告诉我 未使用的导
  • 将图与热图(可能是对数)配对?

    How to create a pair plot in Python like the following but with heat maps instead of points or instead of a hex bin plot
  • 如何从邻接表高效创建稀疏邻接矩阵?

    我正在与last fm http labrosa ee columbia edu millionsong lastfm数据集来自百万歌曲数据集 http labrosa ee columbia edu millionsong 数据以一组 j
  • 将 Django 中的所有视图限制为经过身份验证的用户

    我是 Django 新手 我正在开发一个项目 该项目有一个登录页面作为其索引和一个注册页面 其余页面都必须仅限于登录用户 如果未经身份验证的用户尝试访问这些页面 则必须将他 她重定向到登录页面 我看到 login required装饰器会将
  • PyQt5按钮lambda变量变成布尔值[重复]

    这个问题在这里已经有答案了 当我运行下面的代码时 它显示如下 为什么 x 不是 x 而是变成布尔值 这种情况仅发生在传递到用 lambda 调用的函数中的第一个参数上 错误的 y home me model some file from P
  • py2exe ImportError:没有名为 的模块

    我已经实现了一个名为 myUtils 的包 它由文件夹 myUtils 文件 组成 init py 和许多名称为 myUtils 的 py 文件 该包包含在 myOtherProject py 中 当我从 Eclipse 运行它们时可以找到
  • PyObjC + Python 3.0 问题

    默认情况下 Cocoa Python 应用程序使用默认的 Python 运行时版本 2 5 如何配置我的 Xcode 项目以便它使用较新的 Python 3 0 运行时 我尝试用新版本替换项目中包含的Python framework 但它不

随机推荐

  • 数据库中的连接查询方式(基本)

    1 问题描述 在数据库的学习中 我们知道数据库涉及到多表查询的时候需要用到不同的连接查询方式 SQL中将连接分成 内连接 外连接 自然连接 交叉连接 其中内连接与外连接是比较常用的连接 内连接 从左表中取出数据去跟右表中的所有记录进行匹配
  • ChatGPT大解密:带您探讨机器学习背后的秘密、利用与发展

    一 什么是机器学习 二 ChatGPT 的运作原理 三 ChatGPT 生活利用 1 自然语言处理 2 翻译 3 自动回复 四 ChatGPT vs 其他相关技术 五 ChatGPT 的未来 1 未来发展 2 职业取代 3 客服人员 4 翻
  • Loaded runtime CuDNN library: 8.0.5 but source was compiled with: 8.1.0. CuDNN library needs to hav

    2021 08 16 Loaded runtime CuDNN library 8 0 5 but source was compiled with 8 1 0 CuDNN library needs to have matching ma
  • 行业经验

    原文地址 http www blogjava net ITdavid archive 2008 01 21 176730 html 很多职场新人都谈到了工作经验的问题 似乎招聘公司不给你机会 你就没办法获得必要的工作经验 其实并不一定 很多
  • 前端用js实现粘贴图片实现上传图片功能

    前端实现粘贴复制图片 引入实现粘贴复制功能的外部js文件 设置放置图片的盒子 监听粘贴事件 实现上传功能 粘贴即上传 实现效果截图 引入实现粘贴复制功能的外部js文件 引入复制的外部js文件 设置放置图片的盒子 给放置图片的盒子设置可编辑里
  • React (七)

    React 组件 API 在本章节中我们将讨论 React 组件 API 我们将讲解以下7个方法 设置状态 setState 替换状态 replaceState 设置属性 setProps 替换属性 replaceProps 强制更新 fo
  • Qt5.15.2安装

    解释一下 Qt 的版本号 比如 5 15 2 是完整的 Qt 版本号 第一个数字 5 是大版本号 major 第二个数字 15 是小版本号 minor 第三个数字 2 是补丁号 patch 只要前面两个数字相同 Qt 的特性就是一致的 最后
  • Jenkins (二)

    Jenkins 二 使用pipeline script 简单编译 发布war工程到远程tomcat中 配置所需 下载 apache maven 3 9 3 tar gz 解压 apache maven 3 9 3 bin tar gz 拷贝
  • XP 和 win10 系统 bios配制

    因搞嵌入式 家里有好多老式MCU 用到XP系统相关的编译器和相关的调试工具 专门买了台X230 配两个硬盘XP和win10 发现BIOS配置不一样 在这里记录一下 1 config 下面的 serial ATA 系统 serial ATA
  • 工作流选型

    对比 Activiti Flowable
  • 什么是护网(HVV)_需要什么技能?

    HVV介绍 1 什么是护网 护网的定义是以国家组织组织事业单位 国企单位 名企单位等开展攻防两方的网络安全演习 进攻方一个月内采取不限方式对防守方展开进攻 不管任何手段只要攻破防守方的网络并且留下标记即成功 直接冲到防守方的办公大楼 然后物
  • spring-boot 实现定时任务@Scheduled

    Scheduled 只适合处理简单的计划任务 不能处理分布式计划任务 优势 是spring框架提供的计划任务 开发简单 执行效率比较高 且在计划任务数量太多的时候 可能出现阻塞 崩溃 延迟启动等问题 启动类中加入 EnableSchedul
  • 【转载】Linux 之 makefile详细教程

    什么是makefile 或许很多Winodws的程序员都不知道这个东西 因为那些Windows的IDE都为你做了这个工作 但我觉得要作一个好的和 professional的程序员 makefile还是要懂 这就好像现在有这么多的HTML的编
  • docker run -it 和 docker exec -it具有什么功能呢?

    Docker Docker是一个开源的应用容器引擎 让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中 然后发布到任何流行的 Linux或Windows操作系统的机器上 也可以实现虚拟化 容器是完全使用沙箱机制 相互之间不会有任何接口
  • 有关联想拯救者Y7000重装window10系统

    文章目录 1 联想拯救者使用U盘重装系统不需要进入bios 2 总结 由于C盘爆满了 所以选择重装系统来重新给C盘分下区 给C盘分大点 然后重装系统的具体流程参照的是博客使用U盘重装Windows10系统详细步骤及配图 官方纯净版 然后写这
  • 03 什么是预训练(Transformer 前奏)

    博客配套视频链接 https space bilibili com 383551518 spm id from 333 1007 0 0 b 站直接看 配套 github 链接 https github com nickchen121 Pr
  • 20145334赵文豪《网络对抗》—— 网络欺诈技术防范

    1 实验后回答问题 1 通常在什么场景下容易受到DNS spoof攻击 局域网内的攻击 连接公共场所的wifi 2 在日常生活工作中如何防范以上两种攻击方法 输入个人信息前 仔细检查核对域名是否正确 使用入侵检测系统 使用防火墙进行保护 d
  • 关闭bat运行python时的chromedriver窗口

    运行python 查看三方库的安装位置 打开文件位置 默认conda在这里 找到selenium webdriver common service py 添加依赖 修改70行左右代码 self process subprocess Pope
  • 深度学习之心得——dropout

    作用 防止过拟合 什么时候过拟合 网络参数多 训练数据少的时候 容易过拟合 原理 前向传播过程中暂时屏蔽一些节点 暂时不更新它的参数 这样就可以训练多个不同的网络 降低过拟合的可能 Dropout层的位置 Dropout一般放在全连接层防止
  • python垃圾回收 (GC) 机制

    Python 能够自动进行内存分配和释放 但了解 python 垃圾回收 garbage collection GC 的工作原理可以帮助你写出更好更快的 Python 程序 Python 使用两种算法进行垃圾回收 分别是引用计数 Refer