python中的垃圾回收机制

Python中的垃圾回收(Garbage Collection,简称GC)机制是一个自动内存管理过程,它负责在对象不再被使用时释放内存资源。Python的垃圾回收主要依赖于引用计数(Reference Counting)来跟踪和回收不再使用的对象。除了引用计数,Python的垃圾回收器还包括一个循环检测器,用于检测并回收循环引用中涉及的对象。

对象的底层C语言结构体

在Python中,我们知道一切皆对象,那么在在CPython实现中(CPython是Python的官方和最广泛使用的实现),每个对象确实都是由一个C语言结构体表示的。包括数字、字符串、函数等。这些对象在底层都对应着一个C语言结构体——PyObject

PyObject

PyObject 是所有Python对象共有的部分,它定义了对象的引用计数和类型指针。引用计数用于垃圾回收,而类型指针则指向一个表示该对象类型信息的结构体。

typedef struct _object {_PyObject_HEAD_EXTRAPy_ssize_t ob_refcnt;struct _typeobject *ob_type;
} PyObject;
  • ob_refcnt 是一个引用计数器,它帮助Python进行自动内存管理。
  • ob_type 指向一个表示该对象所属类型的结构体。

PyVarObject

对于那些大小可变的数据类型(如列表、字典等),Python使用了另一种结构体——PyVarObject。这个结构体继承自 PyObject, 并添加了额外的成员来记录变量长度。

typedef struct {PyObject_VAR_HEADPy_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;
  • ob_size 表示容器中元素数量。

类型特定结构体

除了通用部分之外,每种具体数据类型还有自己特定信息需要存储。例如:

  • 对于整型(PyLongObject):

    typedef struct {PyObject_VAR_HEADdigit ob_digit[1];
    } PyLongObject;
    
  • 对于字符串(PyUnicodeObject):

    typedef struct {// ... 其他成员 ...Py_ssize_t length;           /* Length of raw Unicode data */wchar_t *data;               /* Raw Unicode buffer */// ... 其他成员 ...
    } PyUnicodeObject;
    

每种数据类型都会有其专门设计的C语言结构来存储其特定信息和行为方法(如整型数字具有不同大小和符号性质;字符串则需要记录字符序列及长度等)。通过这样丰富多样化地设计内部数据结构与算法实现细节,Python能够以统一且高效率地方式处理各类数据操作与交互逻辑。

小结

  1. ob_refcnt:这是一个整数,表示对象的引用计数。当有新的引用指向对象时,这个计数器会增加;当引用被删除时,计数器会减少。当引用计数降到0时,对象将被垃圾回收器回收。

  2. ob_type:这是一个指向PyTypeObject结构体的指针,它包含了对象的类型信息。PyTypeObject结构体包含了与对象类型相关的数据,如类型名称、大小、类型方法、基类等。

  3. ob_size:这是一个用于跟踪对象中元素数量的字段,它主要用于序列类型(如列表和元组)。对于非序列类型的对象,这个字段可能没有实际用途。

  4. 数据:这是对象实际数据存储的地方。具体布局取决于对象的类型。例如,对于整数对象,数据可能直接存储为整数值;而对于复合类型,数据可能包括指向其他对象的指针。

引用计数

基本概念

引用计数是一种简单直观的内存管理技术,它通过跟踪每个对象被引用的次数来决定对象是否可以被垃圾回收。引用计数是Python垃圾回收机制的核心。

通过上面的结构体我们知道每个对象都有一个引用计数,用于记录有多少个引用指向该对象。当对象被创建时,引用计数初始化为1。如果有新的引用指向该对象,引用计数增加;如果引用被删除,引用计数减少。当对象的引用计数降为0时,意味着没有任何引用指向该对象,对象将立即被回收,其占用的内存被释放。

  1. 创建对象时:当你创建一个新对象时(比如a = 3),Python会为这个对象分配内存,并将其引用计数设置为1。
  2. 增加引用:如果有其他变量被赋值为这个对象(例如b = a),该对象的引用计数会增加。
  3. 减少引用:如果对该对象的一个引用被销毁或者被重新赋值(例如a = 5),则原来指向那个对象的引用计数会减少。
  4. 回收内存:一旦某个对象的引用计数变为0,意味着没有任何变量或者数据结构再指向它,Python就会自动释放掉这块内存。

引用计数机制不能自动处理循环引用的情况,即两个或多个对象相互引用,导致它们的引用计数永远不会降到0。为了解决这个问题,CPython使用了一个循环垃圾收集器。这个收集器会定期运行,通过一个标记-清除算法来识别和回收循环引用的对象。

查看引用计数

在Python中,你可以通过标准库中的sys模块来查看和操作一个对象的实际参考次数。以下是一些例子:

import sys# 创建一个列表
a = []# 查看列表a 的当前参考次数
print(sys.getrefcount(a))  # 注意: 调getrefcount本身也会临时增加一次参考, 所以结果比预期多1# 创建另外两个别名指向同一列表
b = a
c = a# 再次检查参考次数量
print(sys.getrefcount(a))  # 现在应该是4了: a, b, c 和 getrefcount 的临时调用# 删除其中一个别名减少参考数量
del b
print(sys.getrefcount(a))  # 参考数量减少到3了: a, c 和 getrefcount 的临时调用

循环垃圾收集器(Cycle-GC)

为了解决引用计数无法处理循环引用的问题,Python引入了循环垃圾收集器。这个收集器使用一种称为“标记-清除”(Mark-Sweep)的算法来检测并回收循环引用的对象。这个收集器主要针对容器对象,如列表、字典、类实例等,因为这些类型的对象更可能形成循环引用。

算法

循环垃圾收集器基于“标记-清除”(Mark-Sweep)算法

  1. 标记阶段:从一组根对象开始(如全局变量、活动栈帧等),遍历所有可达对象。"可达"意味着从根对象出发可以通过某种方式访问到该对象。在遍历过程中,每访问到一个对象就将其标记为活动的。注意这一阶段,并不是将“标记”的对象放入某个容器中等待回收。
  2. 清除阶段:遍历所有对象,将未标记的对象视为垃圾进行回收。未标记的对象即为那些在标记阶段没有从根对象可达的对象,即不再被任何活动引用所引用。这一步骤并不涉及先存放在某个容器或列表中;相反,一旦确定为垃圾,就会立刻进行资源回收处理。

触发时机

循环垃圾收集(Cyclic Garbage Collector)在Python中并不是以一个独立的监控线程的形式存在。它是Python内存管理机制的一部分,特别是在CPython实现中,它与主程序运行在同一个线程中。循环垃圾收集器会根据特定条件被触发执行,例如当分配操作计数达到某个阈值时。

  • 循环垃圾收集器通常在分配了一定数量新对象或者释放了一定数量旧对象之后自动触发。
  • Python也提供了手动触发GC的接口gc.collect(),允许开发者根据需要控制GC执行时间点。

分代回收(Generational GC)

Python的垃圾回收器还采用了分代回收策略,将对象分为三代:0代、1代和2代。新创建的对象属于0代,如果它们经过一次垃圾回收仍然存活,将被移动到1代,再经过一次回收仍然存活的对象则被移动到2代。分代回收的思想是,存活时间越长的对象,被回收的可能性越小。因此,垃圾回收器会更频繁地回收低代对象,而较少地回收高代对象。

尽管引用计数很高效,但它无法解决循环引用问题。例如两个对象相互引用,即使它们已经不再使用了,由于彼此持有对方的一个有效引用导致其引用计数永远不会降到0。为了解决这一问题,在基于引用计数之上,Python还采取了分代收集策略。

  • 第0代(Generation 0):这里包含所有新创建的对象。由于许多对象会很快变得不可达,因此第0代会频繁进行垃圾回收。
  • 第1代(Generation 1):当一个对象在第0代经历过一次垃圾回收后仍然存活,它会被移动到第1代。相比于第0代,这里的垃圾回收频率较低。
  • 第2代(Generation 2):类似地,从第1代存活下来的对象会被移动到更稳定、更少进行垃圾回收操作的第2 。
  • 如果在第一代中进行了垃圾回收仍然存活下来,则将其移动到第二代。同样地,在第二代中存活下来后会移动到第三代。随着世代等级增加, 对象在内存中存在时间越长。
  • 分代算法假设生命周期短暂(如局部变量)或非常长久(如全局配置信息) 的数据较多, 因此通过调整各世代触发GC(Garbage Collection) 的阈值可以有效提高GC性能。
  • 当执行分代回收时, Python会检查较年轻一些世界里面是否存在循环应用并清除那些无法访问到达外部状态 (unreachable) 的循环。

分层次进行GC

通过将新创建和短命命期预期高、以及长寿命预期高但数量相对较少且稳定性强等特点区别开来处理和管理内存资源使用情况:

  • 在每一次GC执行时,并非检查所有三个世界级别上所有容器型数据结构实例状态;而是首先针对最年轻那一带(即最可能出现大量无效引用关系链条需要清理掉以释放占用资源空间情况)进行检查处理。
  • 只有当年轻带上执行了足够多次GC操作之后才考虑向上逐级提升检查范围至更老旧带级别;并且随着带级别增加其检查频率也随之降低。

使用分层次方法可以显著减少必须要遍历和检查整体数据结构实例状态所需时间和计算资源消耗量——因为大部分新创建实例都可能很快就变成无效引用状态并需要清理掉;同时保证那些确实需要长时间保持有效状态直至程序运行结束阶段才释放掉占用资源空间情况下能够尽可能减少干扰影响。

背后的数据结构

每一代(generation)都有自己的链表来跟踪和管理属于该代的所有对象。这些链表使得垃圾收集器能够有效地遍历特定代中的对象,以执行垃圾回收。

在CPython中,分代回收是通过以下几个关键数据结构来实现的:

  • PyObject: 这是所有Python对象共有的基础结构体。它包含了引用计数和指向其类型描述符的指针。
  • gc模块: Python提供了一个内置模块gc,它暴露了与垃圾收集相关的功能和接口,包括手动触发垃圾回收、调整阈值、查看各代对象列表等。
  • Generation链表: 每一代都有自己独立的链表来跟踪该代中所有活动(即未被回收)对象。当进行垃圾回收时,GC会根据这些链表遍历并检查每一代。每个代的gc_generation结构体还包含两个其他重要的字段:countthresholdcount字段用于跟踪自上次垃圾回收以来分配和释放的对象数量。threshold字段是一个阈值,当count超过这个阈值时,就会触发该代的垃圾回收。

默认阈值

阈值分为三代(Generation 0, 1, 2),每一代都有自己的阈值。这些阈值可以通过gc.get_threshold()函数获取,并且可以通过gc.set_threshold()函数进行设置。

  • 第0代(Generation 0)的阈值通常设置为700。
  • 第1代(Generation 1)的阈值通常设置为0代 10次回收后。
  • 第2代(Generation 2)的阈值通常设置为1代 10次回收后。

调整阈值

开发者可以根据应用程序的需要调整这些阈值。例如,如果你认为应用程序中的对象生命周期更长,你可能希望减少垃圾回收的频率以避免性能开销。在这种情况下,你可以增加阈值。相反,如果你希望更积极地回收不再使用的对象,你可以减少阈值。

import gc# 获取当前的阈值设置
thresholds = gc.get_threshold()
print("当前的gc分代阈值分别是:", thresholds)# 设置新的阈值
new_thresholds = (600, 10, 5)  # 分别为第0、1、2代的阈值, 0代为对象个数,1代和2代为对应前一代的gc次数
gc.set_threshold(*new_thresholds)
thresholds = gc.get_threshold()
print("最新的gc分代阈值分别是:", thresholds)

GC优化

Python的内存管理和垃圾回收机制包括多种优化策略,以提高性能和减少内存使用。其中,对象池(Object Pools)和空闲列表(Free Lists)是两个重要的优化手段。这些技术主要用于管理小对象的分配和回收,因为频繁地为小对象分配和释放内存会导致大量的性能开销。

对象池

前面的文章提到过:https://blog.csdn.net/weixin_39743356/article/details/136077846

Python中最著名的对象池是针对小整数(Small Integers)和短字符串(Short Strings)的优化。

  • 小整数池:Python解释器启动时会创建一个范围在[-5, 256]之间的整数对象池。当程序需要这个范围内的任何整数时,Python都会从这个池中返回相应的引用而不是新建一个对象。这样做可以避免频繁创建和销毁常用数字对象。

  • 短字符串驻留:对于一些内容相同且长度较短的字符串,在程序运行期间只会创建一次并被重复使用。这种机制称为字符串驻留(String Interning),它可以帮助节省内存并加速字典类型键值对查找。

空闲列表

空闲列表是另一种优化技术,主要用于快速分配与回收特定类型对象所占用的内存空间。

  • 列表、字典、集合等容器类型:当这些容器被销毁时,它们占据的内存不会立即返回给操作系统,而是保留在一个空闲列表中。下次再创建同类型容器时,就可以直接重用这块内存区域。

  • 元组: Python还维护了一个专门针对元组大小不同情况下可复用数据块状态记录表——每当有新元组需要创建且其大小符合某个已存在记录项所描述状态时,则直接从该记录项关联空闲列表中取出一块数据区域进行初始化使用;反之则将不再需要且大小符合某记录项描述状态得到旧元组数据区域归还至相关联空闲列表备份后续可能需求。

内部碎片处理

在Python中,内部碎片(Internal Fragmentation)是指在内存分配过程中,由于分配的内存块不能完全被使用而导致的内存空间浪费现象。这种情况通常发生在使用固定大小的内存块分配策略时,例如,当一个对象所需的内存大小不是内存块大小的整数倍时,就会在分配的内存块中留下未使用的部分,这就是内部碎片。

除了上述策略外,CPython还通过“块”(block)管理来减少因小规模分配造成的外部碎片问题:

  • PyMalloc是Python内存管理系统中的一个组件,它负责处理对象的内存分配请求。PyMalloc的设计目的是为了优化小对象的内存分配,减少内存碎片,提高内存分配的效率。

    在Python的内存管理中,对象根据大小被分为不同的类别。PyMalloc主要负责分配小于某个阈值(通常是256字节)的小对象。它使用了一个称为“池”(pool)的数据结构来管理这些小对象的内存分配。这些池被组织成多个“块”(block),每个块包含多个大小相同的对象。

    当Python代码创建一个小对象时,PyMalloc会尝试在一个已有的块中找到一个足够大的空闲空间来放置这个对象。如果没有合适的空闲空间,PyMalloc会分配一个新的块来满足请求。这种分配策略有助于减少内存碎片,并提高内存分配的速度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/800263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows下python3安装rolabelimg或者labelimg2标注斜框

1.前言 大家常用labelimg标注物体,但当目标物体是倾斜角度时,labelimg标注时会把不属于物体的一大片区域也标注,这样标注效果不大好,那么有什么工具可以标注倾斜的矩形框呢,本篇通过介绍安装rolabelimg或者labelimg2&…

创建型模式--3.工厂模式 【人造恶魔果实工厂2】

1. 简单工厂模式的弊端 在上一节简单工厂模式中,创建了一个工厂类,用于生产需要的对象,但是这种方式有一个弊端,它违反了设计模式中的开放-封闭原则,先来看相关的代码: // 恶魔果实工厂类 enum class Typ…

初识虚拟机:探索数字世界的神奇工具

前言 在计算机科学领域,虚拟机是一种强大而神奇的工具,它可以让我们在一台计算机上同时运行多个操作系统,仿佛创造了一个独立的数字世界。本文将带你深入探索虚拟机,帮助你更好地理解这个让计算机变得更加灵活多样的工具。 什么…

使用美化方法设计项目主窗体(二)

使用美化方法设计项目主窗体 分析效果图的实现 效果图: 新建 Windows 窗体 新窗体命名:FrmMain.cs修改窗体的位置:StartPosition:CenterScreen窗体的无边框设计:FormBorderStyle:none修改窗体的大小&a…

Go语言并发(一)——goroutine与Waitgroup

协程 前言 在很多语言中都会提到并发的概念, 例如python, Java,C等等,一般来说 都会 使用多线程或多进程来 实现并发调度,但是多线程/进程 一般会耗费大量内存, 而在go 语言中我们可以使用协程来达到并发调度的目的&…

高级优化理论与方法(七)

高级优化理论与方法(七) Solving Linear EquationsCase 2TheoremKaczmarzs AlgorithmTheoremExample PseudoinverseDefinitionSpecial Case 1Special Case 2 Properties of PseudoinverseLemma 1: Unique pseudoinverseLemma 2: Full Rank Factorization…

Pytorch实用教程:Pytorch中model.eval()和torch.no_grad()的作用及用法

文章目录 1. model.eval()为什么需要 .eval() 方法?使用 .eval() 方法示例 注意事项 2. torch.no_grad()为什么需要 torch.no_grad()?使用 torch.no_grad()示例场景注意事项 1. model.eval() model.eval() 在 PyTorch 中是一个重要的方法,用…

MySQL8.3.0 主从复制方案(master/slave)

一 、什么是MySQL主从 MySQL主从(Master-Slave)复制是一种数据复制机制,用于将一个MySQL数据库服务器(主服务器)的数据复制到其他一个或多个MySQL数据库服务器(从服务器)。这种复制机制可以提供…

如何让阿里云AI001号员工帮我写代码(含IDEA插件使用)

国内首个AI程序员入职阿里云:专属工号AI001,KPI是一人写完公司20%代码。 不管是真是假,AI 程序员发展的趋势是无法改变的,小米汽车发布会上,雷军说到小米汽车工厂的自动化率达到90%以上,有些车间甚至100%的…

手术麻醉系统源码 医疗信息管理系统源码C#.net6.0+ vs2022,vscode+BS网页版 手麻系统源码

手术麻醉系统源码 医疗信息管理系统源码C#.net6.0 vs2022,vscodeB/S网页版 手麻系统源码 手术麻醉管理系统是应用于医院手术室、麻醉科室的计算机软件系统。该系统针对整个围术期,对病人进行全程跟踪与信息管理,自动集成病人HIS、LIS、RIS、PACS信息&…

jdk8新特性 方法引用

简介 lambda表达式是用来简化匿名内部类的方法引用 使用来简化 lambda表达式的 方法引用的标志 两个冒号 静态方法 静态方法 class CompareByAge {public static int compare(Student o1, Student o2) {return o1.getAge() - o2.getAge();} }静态方法引用 Arrays.sort(students…

表格比对作业指导书 使用access对excel表格数据进行比对

初级代码游戏的专栏介绍与文章目录-CSDN博客 (注:这是以前给秘书写的作业指导书,用来处理两个表格中哪些人存在、哪些人不存在。看起来当时使用的access版本是2016。access是微软office套件中的一个软件,存在于家庭版&#xff0c…

探秘Vue异步组件,深入解析

基本用法​ 在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了defineAsyncComponent方法来实现此功能: import { defineAsyncComponent } from vueconst AsyncComp defineAsyncComponent(() >…

​SCP收容物041~050​

注 :此文接SCP收容物031~040,本文只供开玩笑 ,与steve_gqq_MC合作。 --------------------------------------------------------------------------------------------------------------------------------- 目录 scp-041 scp-042 scp-043 scp-044 scp-045…

二维相位解包理论算法和软件【全文翻译- 噪声滤波(3.53.6)】

3.5 噪音过滤 在本节中,我们将简要讨论相位数据的滤波问题。除了提高信噪比之外,噪声滤波还有助于减少残差的数量,从而大大简化相位解包过程。不过,我们必须注意到一个重要的问题。正如我们在第 1 章中指出的,相位本身并不是信号。它只是信号的一种属性。因此,应该过滤的…

JSON字符串中获取一个特定字段的值

JSON字符串中获取一个特定字段的值 一、方式一,引用gson工具二、方式二,使用jackson三、方式三,使用jackson转换Object四、方式四,使用hutool,获取报文数组数据 一、方式一,引用gson工具 测试报文&#xf…

表单流程管理系统:推进数字化转型理想助手

在数字化转型新时代,谁拥有理想的软件平台助手,谁就能在流程化管理新进程中迈出坚实的步伐。面对激烈的市场竞争,低代码技术平台及表单流程管理系统正在广阔的市场环境中越扎越稳,成为助力企业数字化转型升级的重要利器设备。想要…

使用PyCharm安装并运行python程序(小白专属教程,建议收藏)

本文将介绍如何使用pycharm安装python环境并运行第一个python程序,适合刚接触python的童鞋参考。 Python的安装 python是一门跨平台的语言,如Windows、Linux、MacOS等平台都能完美兼容,以下只对Windows平台安装做详细介绍。 1.…

开创加密资产新纪元:深度解析ERC-314协议

随着加密资产市场的不断发展和区块链技术的日益成熟,新的协议和标准不断涌现,其中包括了ERC-314协议。本文将深入分析ERC-314协议的特点、功能以及对加密资产市场可能产生的影响。 1. ERC-314协议简介 ERC-314协议是一项建立在以太坊区块链上的新提案&a…

鲁大师2024年Q1季度电动车报告:新老品牌角逐电自市场,九号699分夺魁

鲁大师2024年Q1季报正式发布,本次季报包含电动车智能排行,测试的车型为市面上主流品牌的主流车型,共计12款,全部按照评测维度更广、更专业的鲁大师电动车智慧评测2.0进行评分,测试的成绩均来自于鲁大师智慧硬件实验室。…