Python中的内存管理:深入分析垃圾回收机制

python中有一个名为refchian的环状双向链表,python运行时创建的所有对象都会添加到refchain中。在refchain中的对象PyObject里都有一个ob_refcnt用来保存当前对象的引用计数器,就是该对象被引用的次数,当对象有新引用时ob_refcnt就会增加,当引用他的对象被销毁时,ob_refcnt就会减少。当引用计数器为0时,该对象就会被销毁。

// python对象的核心结构体PyObject
// 源码 Include/object.h
#define PyObject_HEAD                   PyObject ob_base;
#define PyObject_VAR_HEAD      			PyVarObject ob_base;
// 构造一个双向链表
#define _PyObject_HEAD_EXTRA            \
struct _object *_ob_next;                     \
struct _object *_ob_prev;
typedef struct _object {_PyObject_HEAD_EXTRA    	// 构造双向链表Py_ssize_t ob_refcnt;       // 引用计数器PyTypeObject *ob_type;     	// 数据类型
} PyObject;typedef struct {PyObject ob_base;Py_ssize_t ob_size; 	/* Number of items in variable part 列表、元组等元素的个数 */
} PyVarObject;
// 对象创建时都会有PyObject,列表、元组、字典、集合都会有PyVarObject

一、引用计数器

Python通过引用计数来保存内存变量追踪,记录该对象被其他使用的对象引用的次数,内部有个跟踪变量叫做引用计数器,每个变量有多少个引用,简称引用计数。当某个引用计数为0时,就列入了垃圾回收队列。

import sys
a=1
sys.getrefcount(a) # ==> 2 
# getrefcount返回变量的调用次数,调用时内部会产生临时变量,所以调用次数是2

1、引用计数增加的情况

1、一个对象被分配给一个新的变量 a = 1, b = a
2、对象被放入一个容器中 list.append(a) set.add(a)
3、对象被当作参数传到函数中

2、引用计数减少的情况

1、使用del 显示的删除对象 del a
2、对象所在的容易被删除 list=[a, b] del list
3、引用超出作用域,或者被重新赋值 a = [1,2] a = [3,4]
引用计数器的问题是不能解决两个对象相互引用对象引用自己的情况,del可以减少引用次数,但计数不会归0。

3、GIL存在的关键因素

del 操作时先执行 DELETE_NAME将对象的的引用计数减1,然后再判断对象的引用数是否为0,如果为0会触发垃圾回收,表面del 操作底层是有两步的。

import dis
dis.dis("del a")1           0 DELETE_NAME              0 (a)2 LOAD_CONST               0 (None)4 RETURN_VALUE

现在如果有两个线程A和线程B,同时对data对象进行del data操作时,线程A先执行 del data后执行了DELETE_NAME,引用计数为0,然后发生了CPU调度B线程执行,也对data执行了del data,结果发现data的引用计数已经为0 了,就直接触发垃圾回收,完了后又切换到线程A执行,此时A也会继续判断data的引用数是否为0,然后进行释放,此时data就会变成野指针,这就是二次释放。为了解决这种问题,引入了GIL,保证每一个时刻只有一个线程在解释器中执行,并且会保证线程切换的时候会把当前的指令执行完再进行切换,就不会发生二次释放的问题。
相同的问题,Python的一个字节码可能会对应C中的多个函数调用,GIL也会保证在线程切换时,执行完当前的底层函数调用。

二、标记-清除

1、堆区和栈区

**堆 **Python中的大部分对象(例如列表、字典、类实例,以及小整数池、短字符缓存区、匿名列表对象缓存区、匿名字典对象缓存区)都存储在堆内存中。堆内存用于存储动态分配的对象,其大小通常由Python的内存管理器自动调整。当你创建一个新的对象时,Python会在堆内存中分配内存空间来存储该对象。
内存用于存储函数调用的上下文信息。每当你调用一个函数时,其局部变量、函数参数、返回地址等信息都会被压入栈内存中。当函数执行完毕时,这些信息会被从栈内存中弹出,控制权返回到调用函数。
例子:
data = “hello world.”
info = data

data = “hello world.” 通俗的讲就是等号=右边的值"hello world."存在堆区,而"hello world."所处的内存地址是存在栈里的。data变量就是对"hello world."对象的引用。

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

a和b存在循环引用,当执行del操作后,他们的计数器不会为0,所以永远不会被消除,如果代码中存在很多这种代码就会导致内存被耗尽,程序崩溃。可能存在循环引用的类型有列表、元组、字典、集合、自定义类

2、标记

垃圾回收器会使用深度优先搜索来遍历,当前程序的所有栈区引用的对象,将遍历到的对象标记为存活,表示可以访问到。标记一个对象后,垃圾回收器会继续遍历该对象引用的其他对象。
扩展:什么是三色标记算法?

3、清除

当所有的对象都标记完时,垃圾回收器会扫描整个堆区,清除没有被标记的对象,这些对象都是没有被栈区引用的,这些对象就是要被清除的对象。

4、什么情况下会触发标记-清除呢

垃圾回收阶段会暂停程序,等标记清除后才会恢复程序运行,为了减少程序的暂停时间,python通过分代回收以空间换时间提高垃圾回收效率。

三、分代回收

python将可能存在循环引用的容器对象(内部可以引用其他对象的对象,PyListObject、PyDictObject、自定义类对象、自定义类对象的实例对象)拆分成3个链表,分别为0代、1代、2代总共三代,每代都有可以存对象和阈值,当达到阈值时就会扫描链表,将循环引用各自减一、销毁计数器为0的对象,当第0代扫描后存活下来的对象会被移到第1代,在第1代存活下来的对象会被移到第2代,可以简单的理解为:对象存在时间越长,越可能不是垃圾,应该越少去收集。

// 源码 Modules/gcmodule.c
struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head,                                    threshold,    count */
{{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)},   700,        0},
{{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)},   10,         0},
{{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)},   10,         0},
}// python源码
import gc
gc.get_threshold()  ## 分代回收机制的参数阈值设置
(700, 10, 10)

1、这种对象新创建的时候,就会被加入到0代链表上,当0代链表上的对象数大于700时,就开始扫描0代链表。此时如果2、1代未达到阈值,则扫描0代,并将1代的count值加1,如果2代已经达到阈值,则将2、1、0代三个链表拼接起来进行扫描,并将2、1、0代的count值置为0,如果1代已经达到阈值,则将1、0两个链表拼接起来进行扫描,并将1、0代的count值置为0。
2、当第0代被扫描10次时,则第1代开始扫描。
3、当第1代被扫描10次时,则第2代开始扫描。
对拼接起来的链表在进行扫描时,主要就是剔除循环引用和销毁垃圾,详细过程为:

  • 扫描链表,把每个对象的引用计数器拷贝一份并保存到 gc_refs中,保护原引用计数器。
  • 再次扫描链表中的每个对象,并检查是否存在循环引用,如果存在则让各自的gc_refs减 1 。
  • 再次扫描链表,将 gc_refs 为 0 的对象移动到unreachable链表中;不为0的对象直接升级到下一代链表中。
  • 处理unreachable链表中的对象的 析构函数 和 弱引用,不能被销毁的对象升级到下一代链表,能销毁的保留在此链表。
    • 析构函数,指的就是那些定义了__del__方法的对象,需要执行之后再进行销毁处理。
    • 弱引用,
  • 最后将 unreachable 中的每个对象销毁并在refchain链表中移除(不考虑缓存机制)。

四、弱引用

弱引用与普通引用不同,弱引用不会增加被引用对象的引用计数,因此不会阻止对象被回收。在Python中可以使用weakref模块来创建和操作弱引用,弱引用的主要用途是解决循环引用问题。
1、支持弱引用的对象
对于list、dict、str本身不支持弱引用,但可以通过创建子类的方式对其进行弱引用,对于int、tuple本身及其子类均不支持弱引用,set直接支持弱引用。

import sys
import weakref
a = {1,2,3}
b = a
sys.getrefcount(a) # 3 a被引用的3次c = weakref.ref(a) # 对a进行弱引用 引用次数不会增加
sys.getrefcount(a) # 3

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

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

相关文章

SDK 窗口程序创建

目录 Windows 窗口 窗口的基本概念 创建一个窗口的流程 句柄 创建窗口 设计注册窗口类 创建窗口 显示和更新窗口 创建消息循环 消息循环 建立消息循环 窗口过程函数 窗口程序模板(多字节) 窗口程序模板(Unicode) Wi…

零基础学习HTML5(列表、表格、表单)

01-列表 作用&#xff1a;布局内容排列整齐的区域。 列表分类&#xff1a;无序列表、有序列表、定义列表。 无序列表 作用&#xff1a;布局排列整齐的不需要规定顺序的区域。 标签&#xff1a;ul 嵌套 li&#xff0c;ul 是无序列表&#xff0c;li 是列表条目。 <ul>…

【RTOS学习】信号量 | 互斥量 | 递归锁

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 信号量 | 互斥量 | 递归锁 &#x1f37a;信号量&#x1f964;原理&#x1f964;使用信号量的函数&…

基于springboot实现java学习平台项目【项目源码+论文说明】

基于springboot实现java学习平台演示 摘要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括学习平台的网络应用&#xff0c;在外国学习平台已经是很普遍的方式&#xff0c;不过国内的管理平台可能还处于起步阶段。学习平台具…

使用 Typhoeus 和 Ruby 编写的爬虫程序

以下是一个使用 Typhoeus 和 Ruby 编写的爬虫程序&#xff0c;用于爬取 &#xff0c;同时使用了 jshk.com.cn/get_proxy 这段代码获取代理&#xff1a; #!/usr/bin/env rubyrequire typhoeus require jsondef get_proxyurl "https://www.duoip.cn/get_proxy"respon…

正则表达式[总结]

文章目录 1. 为什么要学习正则表达式2. 再提出几个问题&#xff1f;3. 解决之道-正则表达式4. 正则表达式基本介绍5. 正则表达式底层实现(重要)6. 正则表达式语法6.1 基本介绍6.2 元字符(Metacharacter)-转义号 \\\6.3 元字符-字符匹配符6.4 元字符-选择匹配符6.5 元字符-限定符…

vscode中4个json的区别和联系

在vscode中快捷键ctrlshiftp&#xff0c;然后输入setting&#xff0c;会出现下图几个选项 当不同设置之间出现冲突时&#xff0c;听谁的&#xff1a; Open Workspace Settings(JSON) > Open Settings(JSON) Open User Settings > Open Default Settings(JSON) Open Wo…

WPF实现签名拍照功能

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

如何理解OSI七层模型?

一、是什么 OSI &#xff08;Open System Interconnect&#xff09;模型全称为开放式通信系统互连参考模型&#xff0c;是国际标准化组织 ( ISO ) 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架 OSI 将计算机网络体系结构划分为七层&#xff0c;每一层实现各自…

element-ui中表格树类型数据的显示

项目场景&#xff1a; 1&#xff1a;非懒加载的情况 1&#xff1a;效果展示 2&#xff1a;问题描述以及解决 1&#xff1a;图片展示 2&#xff1a;html <-- default-expand-all 代表默认展开 如果不展开删除就行 --> <el-tableref"refsTable"v-loadin…

Linux_Shell运行原理(命令行解释器)

一般我们叫Linux操作系统&#xff0c;狭义上就是指Linux内核&#xff08;kernel&#xff09;&#xff0c;广义上就是Linux内核Linux外壳程序对应的配套程序&#xff0c;这里我们来详细介绍一下这个“外壳程序”。 在我们使用指令时&#xff0c;这个外壳程序会将我们的解释指令并…

【Arduino TFT】基于 ESP32S3 S7789 240x240 TFT实现的龙猫太空人天气时钟

忘记过去&#xff0c;超越自己 ❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2023-10-21 ❤️❤️ 本篇更新记录 2023-10-21 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言&#x1f4dd;&#x1f64…

【趣味随笔】农业机器人的种类与发展前景

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

关于Mysql中的索引与事务

索引 定义 索引&#xff1a;为了提高查找效率而使用的一种数据结构把数据组织起来&#xff0c;可以把索引理解在书的目录或字典的检索表&#xff08;拼音检索&#xff09; 索引是一种特殊的文件&#xff0c;可以包含着对数据表里的所有记录的引用指针&#xff0c;对表中的一…

重磅发布!RflySim Cloud 智能算法云仿真平台亮相,助力大规模集群算法高效训练

RflySim Cloud智能算法云仿真平台&#xff08;以下简称RflySim Cloud平台&#xff09;是由卓翼智能及飞思实验室为无人平台集群算法验证、大规模博弈对抗仿真、人工智能模型训练等前沿研究领域研发的平台。主要由环境仿真模块、物理效应计算模块、多智能体仿真模块、分布式网络…

代码随想录Day24 LeetCode T491 递增子序列 LeetCode T46 全排列 LrrtCode T47 全排列II

LeetCode T491 递增子序列 题目链接:491. 递增子序列 - 力扣&#xff08;LeetCode&#xff09; 题目思路: 首先这里的测试用例很容易误导我们,这道题不能使用上次子集的思路对数组先排序,使用一个used数组来解决问题. 我们用[4,7,6,7]举例这道题的递增序列不存在[4,6,7,7]这个…

合同管理系统

合同管理系统 功能介绍&#xff1a; 功能特性&#xff1a; 根据对合同管理系统系统分析合同管理由以下模块组成&#xff0c;相对方管理、合同文本管理、合同审批管理、合同履行审批、风险事件管理、合同查询、合同统计、系统提醒、系统管理。 1、相对方管理 1.有“相对方…

SpringBoot环境搭建与初创程序

一&#xff1a;IDEA环境准备 IDEA社区版版本: 2021.1-2022.1.4 IDEA专业版版本: 无要求 &#x1f31f;如果个人电脑安装的IEDA不在这个范围&#xff0c;需要卸载重新安装&#xff1b;且⼀定要删除注册表 参考文章➜IDEA卸载和删除注册表 二&#xff1a; Maven (1)Maven的概念…

第六届“中国法研杯”司法人工智能挑战赛进行中!

第六届“中国法研杯”司法人工智能挑战赛 赛题上新&#xff01; 第六届“中国法研杯”司法人工智能挑战赛&#xff08;LAIC2023&#xff09;目前已发布司法大模型数据和服务集成调度 、证据推理、司法大数据征文比赛、案件要素识别四大任务。本届大赛中&#xff0c;“案件要素…

克隆的虚拟机,查不到IP号

文章目录 问题解决描述解决步骤重新生成MAC地址修改一修改二 相关操作查看当前所有网卡修改网络配置文件文件内容修改修改文件名 问题解决 描述 使用克隆的虚拟机&#xff0c;网卡和原虚拟机的相同&#xff0c;会导致克隆虚拟机的网卡不可用&#xff0c;从而使用ip addr查看不…