首页 > 系统相关 >Python内存管理&垃圾回收机制

Python内存管理&垃圾回收机制

时间:2023-10-01 22:11:36浏览次数:56  
标签:垃圾 v1 Python v2 链表 对象 计数器 引用 内存

Python内存管理&垃圾回收机制

引用计数器为主,标记清除和分代回收为辅 (循环垃圾回收器) + 缓存机制

一、引用计数器

1、环状双向链表 refchain

image-20231001191547777

  • 在python程序创建的任何对象都会放在rechain双向链表中。
name = '七落'
age = 18
hobby = ['篮球', '美女']
# 内部会创建一些数据【上一个对象、下一个对象、类型、引用个数】
name = '七落'

# 内部会创建一些数据【上一个对象、下一个对象、类型、引用个数、val = 18】
age = 18


# 内部会创建一些数据【上一个对象、下一个对象、类型、引用个数、items= '元素'、元素个数】
hobby = ['篮球', '美女']

2、类型封装结构体

data = 3.14

# 内部会创建:
	_ob_next = refchain中下一个对象 
    _ob_prev = refchain中上一个对象
    ob_refcnt = 1 # 引用个数
    ob_type = float
    ob_fval = 3.14
  • 前四个都是对象中共有的值:PyObject结构体(四个值)

3、引用计算器

v1 = 3.14
v2 = 999
v3 = (1,2,3)

当python程序运行时,会根据数据不同的类型找到不同的结构体,根据结构体中的字段来进行创建相关的数据,然后将创建的对象添加到refchain双向链表中。

在C源码中有两个关键结构体:PyObject(通用)、PyVarObject。

每个对象中有的ob_refcnt 就是引用计数器,值默认为1,当有其他变量引用对象时,引用计数器就会加1。

  • 引用:
a = 9999 # 创建对象时初始化引用计数器为1
b = a # 9999的引用计数器 + 1
  • 删除引用
a = 9999
b = a
del b # b变量删除,b对应对象(9999)引用计数器 -1
del a # a变量删除,a对应对象(9999)引用计数器 -a

# 垃圾回收:当一个对象的引用计数器为0的时候,意味着没有人使用这个对象了,这个对象就是一个垃圾,垃圾回收
# 回收:1.对象从refchain双向链表中移除 2.将对象销毁,内存归还

增加引用计数器:

  • 对象被创建时

  • 另外的别名被创建

  • 被作为参数传递给函数(新的本地引用)

  • 成为容器对象的一个元素

减少引用计数器:a = 10

  • 对象的引用被销毁时 (del a)
  • 当变量被赋值给另外的对象 (a = 10,b = a,a = 20)

二、标记清除

1、循环引用问题

循环引用&交叉感染

v1 = [1,2,3] # 在refchain创建列表对象v1,初始化引用计数器为1
v2 = [4,5,6] # 在refchain创建列表对象v2,初始化引用计数器为1

v1.append(v2) # 把v2追加到v1中,则v2对应的[4,5,6]对象的引用计数器+1,最终为2
v2.append(v1) # 把v1追加到v2中,则v1对应的[1,2,3]对象的引用计数器+1,最终为2

del v1 # 引用计数器为-1 
del v2 # 引用计数器为-1 
  • 引用计数器并不能为0,如果只是通过引用计数器的话并不能销毁对象,归还内存

2、标记清除

目的:为了解决引用计数器的循环引用的不足。

实现: 通过在python的底层,再维护一个链表,在链表中专门存放那些可能存在循环引用的对象。(list、tuple、set、dict)

image-20231001200956183

在python内部某种情况情况下触发,回去扫描可能存在循环应用的链表,检查是否有循环引用,如果有则让双方的引用计数器 -1,如果引用计数器为0则垃圾回收。

问题:

  • 什么时候扫描?
  • 可能存在循环引用的链表 扫描代价大,每次扫描耗时久。

三、分代回收

image-20231001202021973

将可能存在的循环引用的对象维护成3个链表:

  • 0代 : 0代中的对象个数达到700个扫描一次
  • 1代: 0代扫描10次,则1代扫描一次。
  • 2代: 1代扫描10次,则2代扫描一次。

比如:当0代中的对象个数达到700时候扫描一次,将存在的循环引用对象的引用计数器-1,若是引用计数器为0则垃圾回收,否则将对象存放到1代中管理。当0代扫描10次后,1代扫描一次,同样存放到2代中管理。

四、小结

在python中维护了一个refchain双向环状链表,这个链表中存储了程序创建的所有对象,每种类型的对象中都有一个ob_refcnt引用计数器的值,引用个数+1 或 - 1,当引用计数器值为0时会进行垃圾回收。(对象销毁,从refchain链表中移除)

但是,在python中对于那些可以有多个元素组成的对象可能会存在循环引用问题,为了解决这个问题,python又引入了标记清除和分代回收,在其内部维护了4个链表:

  • refchain
  • 0代:700个对象
  • 1代:0代扫描10次
  • 2代:1代扫描10次

在源码内部达到各自的阈值后,就会触发扫描链表进行标记清除的动作(有循环引用则引用计数器-1)、

五、Python缓存

1、池(int)

为了避免重复创建或销毁一些常见的对象,维护池。

# 在启动解释器的时候,python内部帮我们创建:-5、-4...257
v1 = 10 # 内存不会开辟内存,直接去池里面取
v2 = 16 # 内存不会开辟内存,直接去池里面取,引用计数器的本来已经被初始化为1了

2、free_list(float/list/tuple/dict)

当一个对象的引用计数器为0时,按理说应该回收。但是内部不会直接回收,而是将对象添加到free_list链表中缓存。以后再创建对象时,不再重新开辟新的空间,而是直接使用free_list。

v1 = 3.14 # 开辟内存,内部存储结构定义值,并存储到rechain结构中

del v1 # 从refchain中移除,把对象添加到 free_list中,free_list满了则销毁

v2 = 9.99 # 不再重新开辟空间,去free_list中获取对象,对象内部数据初始化,再放到rechain中

# 那么v1 与 v2 可能有相同的id内存地址

参考文档:

https://www.cnblogs.com/wupeiqi/articles/11507404.html

http://www.wklken.me/posts/2015/09/29/python-source-gc.html

标签:垃圾,v1,Python,v2,链表,对象,计数器,引用,内存
From: https://www.cnblogs.com/luoluoange/p/17739346.html

相关文章

  • Cplex混合整数规划求解(Python API)
    绝对的原创!罕见的Cplex-PythonAPI混合整数规划求解教程!这是我盯了一天的程序一条条写注释一条条悟出来的•́‸ก一、问题描述求解有容量限制的的设施位置问题,使用Benders分解。模型如下:\[min\quad\sum^{locations}_{j=1}fixedCost_j//open_j+\sum^{locations}_{j=1}\sum^{cli......
  • 如何查找python对象或类的父类子类以及用法
    一个类其方法和数据的来源可以是自定义,也可以是继承自各级父类。通过dir查看其方法和属性,通过help查看其使用方法。特别地,可通过Base和subclass寻找其父类和其他子类。亦可通过文档研究其继承关系。文档不仅包含自身类,也包括其父类的属性方法。  python>>>help(op("/projec......
  • Python笔记:控制流优化
    零值判断Python当中有个语法糖是可以直接对某个对象做空值判断:ifnums_arr: pass不同类型的数据对应什么样的bool值呢?我们可以有如下的判断:None、0、False、空列表、空元组、空字典、空集合等等都对应布尔值为假。其余的对应布尔值为真。但是现在问题来了,对于开发者自......
  • adoc转换html+UPF低功耗仿真例子+python转换C代码+readmemh的@使用
    adoc转换htmladoc这种格式是很多riscv文档使用的格式,该格式可以生成pdf,生成html。生成html的好处是,选中和翻译方便,复制粘贴方便。首先是gem软件要安装,这个软件似乎是ruby相关的(RubyGemsisapackagemanagerfortheRubyprogramminglanguagethatprovidesastandardform......
  • python不能找到自己写的包怎么办
    python找不到自己写的包一般是因为路径问题导致的,我们的包在不同的目录下需要使用不同的方式导入。下面我们就来看一下遇到无法找到自己写的包的解决方法:我们可以先使用下面的方法查看当前路径:importsysprint(sys.path)然后使用下面的方法获取包所在的路径即可:fromosimp......
  • python numpy 稀疏矩阵与密集矩阵
    在NumPy中,稀疏矩阵和密集矩阵是两种不同的数据表示方式,用于存储矩阵数据。它们之间的主要区别在于存储元素的方式和内存占用。稀疏矩阵(SparseMatrix):区别:存储方式:稀疏矩阵只存储非零元素的位置和数值,而忽略零元素,从而节省内存。内存占用:由于只存储非零元素,稀疏矩阵在处理大规模......
  • 5大文件管理操作,Python自动化办公,整明白了
    大家好,这里是程序员晚枫。在更新开源项目:python-office的这1个多月里,又发现了一些新需求,今天整理出来,分享给大家~全是自动化办公的常用工具,网友:早知道就好了1、批量压缩文件夹电脑空间不够用了?别怕,批量压缩一下文件吧~安装第三方库pipinstallpofile代码importpofile......
  • 常见排序的python实现
    常见排序的python实现importnumpyasnpimporttimeitimportmatplotlib.pyplotasplt##生成测试序列defGenerateArray(n,N=1000):orginArray=np.random.randint(N,size=n).tolist()returnorginArrayorginArray=GenerateArray(20)print(orginArray)......
  • python 获取城市所属省份
    获取城市所属省份defgetProvince(cityValue):area_data={'北京':['北京市','朝阳区','海淀区','通州区','房山区','丰台区','昌平区','大兴区','顺义区','西城区','延......
  • 二十四点游戏Python实现
    二十四点游戏是一种数学益智游戏,通过组合四个数字和四种基本运算符(加、减、乘、除),使得计算结果等于24。在本文中,我们将使用Python语言实现这个游戏。一、游戏规则1、从给定的四个数字中选取任意两个数字,并选择一个运算符进行计算。2、将计算结果与剩余的两个数字结合,再选择一个运算......