Python入门【内存管理机制、Python缓存机制、垃圾回收机制、分代回收机制】(三十二)

👏作者简介:大家好,我是爱敲代码的小王,CSDN博客博主,Python小白
📕系列专栏:python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发
📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人

🔥🔥🔥 python入门到实战专栏:从入门到实战 

🔥🔥🔥 Python爬虫开发专栏:从入门到实战

🔥🔥🔥 Python办公自动化专栏:从入门到实战

🔥🔥🔥 Python数据分析专栏:从入门到实战

🔥🔥🔥 Python前后端开发专栏:从入门到实战

目录

内存管理机制

Python缓存机制

垃圾回收机制

分代回收机制


内存管理机制

Python是由C语言开发的,底层操作都是基于C语言实现,Python中创建每个对象,内部都会与C语言结构体维护一些值。 

源码下载,https://www.python.org/

将压缩文件减压,可以看到有很多文件,主要关心两个(Include、 Objects) 在Include目录下object.h中可以查看创建对象的结构体。 

#define _PyObject_HEAD_EXTRA           \struct _object *_ob_next;           \struct _object *_ob_prev;
typedef struct _object {_PyObject_HEAD_EXTRAPy_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;

在创建对象时,每个对象至少内部4个值,PyObject结构体(上一个 对象、下一个对象、类型、引用个数)。

有多个元素组成的对象使用PyVarObject,里面由:PyObject结构体(上一个对象、下一个对象、类型、引用个数)+Ob_size(items=元素,元素个数)。

环状双向链表refchain

在python程序中创建的任何对象都会被放在refchain链表中。

类型封装结构体

f = 3.14
'''
内部会创建:
1.开辟内存
2.初始化ob_fval = 3.14 值ob_type = float 类型ob_refcnt = 1 引用数量
3.将对象加入到双向链表refchain中_ob_next = refchain中的上一个对象_ob_prev = refchain中的下一个对象
'''

Python缓存机制

小整数对象池 

一些整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。

Python 对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象。在一个 Python 的程序中,无论这个整数处于LEGB(局部变量,闭包,全局,内建模块)中的哪个位置, 所有位于这个范围内的整数使用的都是同一个对象。

【示例】验证小整数池

In [8]: a = 100In [9]: id(a)
Out[9]: 140705185112832#删除变量a
In [10]: del aIn [11]: b = 100In [12]: id(b)
Out[12]: 140705185112832

大整数对象池

每一个大整数,均创建一个新的对象。

【示例】验证大整数池

In [13]: a = 257In [14]: b = 257In [15]: id(a)
Out[15]: 1747036560496In [16]: id(b)
Out[16]: 1747036558352In [17]: a is b
Out[17]: False

intern机制

每个单词(字符串),不夹杂空格或者其他符号,默认开启intern机制,共享内存,靠引用计数决定是否销毁。

【示例】intern机制

>>> a = 'helloworld'
>>> b = 'helloworld'
>>> a is b
>>> a = 'hello world'
>>> b = 'hello world'
>>> a is b

free_list机制

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

以上的free_list的代表:float,list,tuple,dict。

>>> f1 = 3.14
>>> id(f1)
2078136427568
>>> del f1
>>> f2 = 9.998
>>> id(f2)
2078136427568

f1 = 3.14,会创建float类型,并且加入refchain中。del f1 则减1, refchain移除,按理会进行销毁,但是实际中不会真正销毁,而是 会添加到free_list。以后创建f2=9.998等,只要是float类型,则不会重新开辟内存,会去free_list获取对象,对象内部进行初始化, 替换值3.14换成9.99,再放到refchain中。不是所有的都放入 free_list。例如存储100个缓存,则前面新创建的80个会放入 free_list。如果满了81之后则会销毁。

1、 float类型,维护的free_list链表最多可缓存100个float对象。

#ifndef PyFloat_MAXFREELIST//定义free_list的最大长度
#define PyFloat_MAXFREELIST 100
#endif

2、 list类型,维护的free_list数组最多可缓存80个list对象。

#ifndef PyList_MAXFREELIST#define
PyList_MAXFREELIST 80
#endif

【示例】

>>> v1 = [11,22,33]
>>> id(v1)
2078137539904
>>> del v1
>>> v2 = ['a','b']
>>> id(v2)
2078137539904

3、 tuple类型,维护一个free_list数组且数组含量20,数组中元素 可以是链表且每个链表最多可以含钠2000个元组对象。元组的 free_list数据在存储数据时,是根据元组可容纳的个数为索引找 到free_list数组中对应的链表,并添加到链表中。

>>> t1 = (1,2,3)
>>> id(t1)
2078137564992
>>> del t1 #元组的数量是3,所以把这个对象缓存到
free_list[3]的链表中
>>> t2 = ('a','b','c')
#元组的数量也是3,不会重新开辟内存,而是去
free_list[3]对应的链表中拿到一个对象来使用
>>> id(t2)
2078137564992
>>> del t2
>>> t3 = (11,22,33,44)
>>> id(t3)
2078138219472

 4、 dict类型,维护的free_list数组最多可缓存80个dict对象。

#ifndef PyDict_MAXFREELIST#define
PyDict_MAXFREELIST 80
#endif

【示例】

>>> d1 = {'name':'zs'}
>>> id(d1)
2078137472640
>>> del d1
>>> d2 = {'age':30}
>>> id(d2)
2078137472640

垃圾回收机制

Python 内部采用 引用计数法 ,为每个对象维护引用次数,并据此回收不再需要的垃圾对象。由于引用计数法存在重大缺陷,循环引 用时有内存泄露风险,因此 Python 还采用 标记清除法 来回收存在循环引用的垃圾对象。此外,为了提高垃圾回收( GC )效率,Python 还引入了 分代回收机制 。 

 引用计数法

Python采用了类似Windows内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数。

引用计数 是计算机编程语言中的一种 内存管理技术 ,它将资源被引用的次数保存起来,当引用次数变为 0 时就将资源释放。它管理 的资源并不局限于内存,还可以是对象、磁盘空间等等。

Python 也使用引用计数这种方式来管理内存,每个 Python 对象都包含一个公共头部,头部中的 ob_refcnt 字段便用于维护对象被引用 次数。回忆对象模型部分内容,我们知道一个典型的 Python 对象结构如下:

当创建一个对象实例时,先在堆上为对象申请内存,对象的引用计数被初始化为 1 。以 Python 为例,我们创建一个 float 对象保存 6.66,并把它赋值到变量 f : 

>>> f = 6.66
>>> f
6.66

由于此时只有变量 f 引用 float 对象,因此它的引用计数为 1 :

当我们把 pi 赋值给 f 后,float 对象的引用计数就变成了 2 ,因为现在有两个变量引用它: 

>>> ff = f
>>> ff
6.66

我们新建一个 list 对象,并把 float 对象保存在里面。这样一来, float 对象有多了一个来自 list 对象的引用,因此它的引用计数又加 一,变成 3 了: 

>>> l = [f]
>>> l
[6.66]

标准库 sys 模块中有一个函数 getrefcount 可以获取对象引用计数:

>>> import sys
>>> sys.getrefcount(pi)
4

 咦!引用计数不应该是 3 吗?为什么会是 4 呢?由于 float 对象被作为参数传给 getrefcount 函数,它在函数执行过程中作为函数的局部变量存在,创建了一个临时的引用,因此又多了一个引用:

随着 getrefcount 函数执行完毕并返回,它的栈帧对象将从调用链中解开并销毁,这时 float 对象的引用计数也跟着下降。因此,当一个 对象作为参数传个函数后,它的引用计数将加一;当函数返回,局部名字空间销毁后,对象引用计数又减一。

引用计数就这样随着引用关系的变动,不断变化着。当所有引用都消除后,引用计数就降为零,这时 Python 就可以安全地销毁对象, 回收内存了: 

>>> del l
>>> del ff
>>> del f

引用计数增加

1、 对象被创建

2 、如果有新的对象使用该对象

3 、作为容器对象的一个元素

4、 被作为参数传递给函数

引用计数减少

1、 对象的引用被显示的销毁

2、 新对象不再使用该对象

3 、对象从列表中被移除,或者列表对象本身被销毁

4 、函数调用结束 

引用计数机制的优点 

1、简单

2、实时性:一旦没有引用,内存就直接释放了。

 引用计数机制的缺点

1、维护引用计数消耗资源

2、循环引用的问题无法解决

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

 标记-清除

引用计数法能够解决大多数垃圾回收的问题,但是遇到两个对象相互引用的情况,del语句可以减少引用次数,但是引用计数不会归 0,对象也就不会被销毁,从而造成了内存泄漏问题。针对该情况, Python引入了标记-清除机制

循环引用 

引用计数这种管理内存的方式虽然很简单,但是有一个比较大的瑕疵,即它不能很好的解决循环引用问题。如上图所示:对象 A 和对 象 B,相互引用了对方作为自己的成员变量,只有当自己销毁时, 才会将成员变量的引用计数减 1。因为对象 A 的销毁依赖于对象 B 销毁,而对象 B 的销毁与依赖于对象 A 的销毁,这样就造成了我们 称之为循环引用(Reference Cycle)的问题,这两个对象即使在外界已经没有任何指针能够访问到它们了,它们也无法被释放。 

class People:pass
class Cat:pass
#创建People的实例对象
p = People()
#创建Dog的实例对象
c = Cat()
#People的宠物属性指向Cat
p.pet = c
#Cat的主人属性指向People
c.master = p
#删除p和c对象
del p
del c

上述实例中,对象p中的属性引用c,而对象c中属性同时来引用p, 从而造成仅仅删除p和c对象,也无法释放其内存空间,因为他们依然在被引用。深入解释就是,循环引用后,p和c被引用个数为2, 删除p和c对象后,两者被引用个数变为1,并不是0,而python只有 在检查到一个对象的被引用个数为0时,才会自动释放其内存,所以 这里无法释放p和c的内存空间。

主动思路一般分为两步:垃圾识别 垃圾回收 。垃圾对象被识别出来后,回收就只是自然而然的工作了,因此垃圾识别是解决问题的关键。那么,有什么办法可以将垃圾对象识别出来呢?我们来考察一个一般化例子:

这是一个对象引用关系图,其中灰色部分是需要回收但由于循环引用而无法回收的垃圾对象,绿色部分是被程序引用而不能回收的活跃对象。如果我们能够将活跃对象逐个遍历并标记,那么最后没有被标记的对象就是垃圾对象。

遍历活跃对象,第一步需要找出 根对象 ( root object )集合。所谓根对象,就是指被全局引用或者在栈中引用的对象,这部对象是不能被删除的。因此,我们将这部分对象标记为绿色,作为活跃对象遍历的起点。 

根对象本身是 可达的 ( reachable ),不能删除;被根对象引用的对象也是可达的,同样不能删除;以此类推。我们从一个根对象出发,沿着引用关系遍历,遍历到的所有对象都是可达的,不能删除。 

 这样一来,当我们遍历完所有根对象,活跃对象也就全部找出来了:

而没有被标色的对象就是 不可达 ( unreachable )的垃圾对象,可以被安全回收。循环引用的致命缺陷完美解决了! 

这就是垃圾回收中常用的 标记清除法 。 

【示例】标记清除法

 上图中小黑点(变量)表示根节点,从根节点出发,每个对象都有引用和被引用的情况,如果该对象找不到根节点,那么就会被清除,如图1,2,3都有被小黑点(变量)引用,4,5没有变量引用,所以 4,5就会被清除。

分代回收机制

Python 程序启动后,内部可能会创建大量对象。如果每次执行标记清除法时,都需要遍历所有对象,多半会影响程序性能。为此, Python引入分代回收机制——将对象分为若干“”( generation ), 每次只处理某个代中的对象,因此 GC 卡顿时间更短。

考察对象的生命周期,可以发现一个显著特征:一个对象存活的时间越长,它下一刻被释放的概率就越低。我们应该也有这样的亲身体会:经常在程序中创建一些临时对象,用完即刻释放;而定义为 全局变量的对象则极少释放。

因此,根据对象存活时间,对它们进行划分就是一个不错的选择。 对象存活时间越长,它们被释放的概率越低,可以适当降低回收频率;相反,对象存活时间越短,它们被释放的概率越高,可以适当提高回收频率。

对象存活时间释放概率回收频率

Python 内部根据对象存活时间,将对象分为 3 代(见 Include/internal/mem.h ):

#define NUM_GENERATIONS 3

 随着时间的推进,程序冗余对象逐渐增多,达到一定阈值,系统进行回收。

这 3 个代分别称为:初生代、中生代 以及 老生代。当这 3 个代初始化完毕后,对应的 gc_generation 数组大概是这样的:

import gc
#python 中内置模块gc触发
print(gc.get_threshold()) #查看gc默认值
#输出(700, 10, 10)

第一代链表:

当第一代达到700,就开始检测哪些对象引用计数变成0了,把不是0的放到第二代链表里,此时第一代链表就是空了,当再次达到700 时,就再检测一遍。

第二代链表:

当第二代链表达到10,就检测一次。

第三代链表:

第三代链表检测10之后,第三代链表检测一次。

import gc#返回一个元组,分别获取这三代当前计数
gc.get_count()  #返回一个元组,分别获取这三代当前的收集阈值
gc.get_threshold()#设置阈值
gc.set_threshold()#关闭gc垃圾回收机制
gc.disable()

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

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

相关文章

回归预测 | MATLAB实现GWO-SVM灰狼优化算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GWO-SVM灰狼优化算法优化支持向量机多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现GWO-SVM灰狼优化算法优化支持向量机多输入单输出回归预测(多指标,多图)效果一览基…

基于Matlab 代码实现UWB信号的产生和调制、接收和检测系统

文末提供完整的matlab代码实现下载链接 介绍 本文将分为两部分介绍。在第一部分中,使用 TR 的建模UWB 信号发生器,生成编码二进制数据的脉冲调制信号。这第二部分涉及对接收第一部分中生成的信号的接收器进行建模提取并显示信息。每个信号的时间图和频率响应绘制了阶段。使用MA…

0101前期准备-大数据学习

文章目录 1 前言2 配置VMware虚拟机2.1 设置主机名和固定IP2.2 本地系统与Linux系统配置主机名映射2.3 配置虚拟机之间用户的SSH免密互通2.4 安装JDK环境2.5 关闭防火墙和SELinux2.6 更新时区和同步时间2.7 保存虚拟机快照 结语 1 前言 我们从基础的hadoop开始学起,…

408反向改考自命题的211学校,计算机招生近500人!今年能捡到漏吗?

贵州大学(C) 考研难度(☆☆☆) 内容:23考情概况(拟录取和复试分析)、院校概况、23专业目录、23复试详情、各专业考情分析。 正文1498字,预计阅读:3分钟。 2023考情概况 贵州大学计算机相关各…

springBoot 配置文件 spring.resources.add-mappings 参数的作用

在Spring Boot应用中,spring.resources.add-mappings参数用于控制是否将特定路径的资源文件添加到URL路径映射中。 默认情况下,该参数的值为true,即会自动将静态资源(例如CSS、JavaScript、图片等)的URL路径添加到Spr…

光栅化之扫描填充三角形

重心坐标计算 重心坐标比较简单,取最大包围合再计算点是否在三角形内就行,再根据重心坐标返回的alpha,beta,gamma三个权重值计算 uv映射和depth深度缓冲值,因为是求的重心坐标,感觉效果比插值的要好一点。 求重心坐标 barycentr…

LeetCode128.最长连续序列

我这个方法有点投机取巧了,题目说时间复杂度最多O(n),而我调用了Arrays.sort()方法,他的时间复杂度是n*log(n),但是AC了,这样的话这道题还是非常简单的,创建一个Hashmap,以nums数组的元素作为ke…

K8S deployment挂载

挂载到emptyDir 挂载在如下目录,此目录是pod所在的node节点主机的目录,此目录下的data即对应容器里的/usr/share/nginx/html,实现目录挂载;图1红框里的号对应docker 的name中的编号,如下俩个图 apiVersion: apps/v1 k…

kafka--kafka的基本概念-副本概念replica

三、kafka的基本概念-副本概念replica Broker 表示实际的物理机器节点 Broker1中的绿色P1表示主分片Broker2中的蓝色P1表示副本分片,其余类似,就是主从的概念,如果一个Broker挂掉了,还有其它的节点来保证数据的完整性 P可以看做分…

带你了解建堆的时间复杂度

目录 用向上调整建堆的时间复杂度 1.向上调整建堆的时间复杂度O(N*logN) 2.数学论证 3.相关代码 用向下调整建堆的时间复杂度 1.建堆的时间复杂度为O(N) 2.数学论证 3.相关代码 完结撒花✿✿ヽ(▽)ノ✿✿ 博主建议:面试的时候可能会被面试官问到建堆时间复杂度的证明过…

【3Ds Max】图形合并命令的简单使用

示例(将文字设置在球体上) 1. 首先这里创建一个球体和一个文本 2. 选中球体,在复合对象中点击图形合并按钮 点击“拾取图形”按钮,然后选中文本,此时可以看到球体上已经投射出文本 3. 接下来是一些常用参数的介绍 当…

13. Vuepress2.x 部署站点的基础路径从配置文件中读取

收到需求,站点要部署到 非根路径 下,且将来会根据 版本号 区分不同的基础路径。需要从统一的文件中读取,方便其它 js 文件和 config.js 配置统一读取。 目录 docs\.vuepress\public\cfg\ 下新建文件 version.js,内容如下 const P…

LeetCode算法递归类—二叉树中的最大路径和

目录 124. 二叉树中的最大路径和 - 力扣(LeetCode) 题解: 代码: 运行结果: 二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该…

LeetCode450. 删除二叉搜索树中的节点

450. 删除二叉搜索树中的节点 文章目录 [450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/)一、题目二、题解方法一:递归(一种麻烦的方法)方法二:优化后的递归 一、题目 给定一个二叉搜索树的根…

干翻Dubbo系列第十二篇:Dubbo协议介绍

文章目录 文章说明 一:Dubbo协议 1:Dubbo协议简介 2:Dubbo协议优点 3:Dubbo协议帧的组成 (一):幻数 (二):2Way (三):event (四):Serilization ID (五):status …

linux:Temporary failure in name resolutionCouldn’t resolve host

所有域名无法正常解析。 ping www.baidu.com 等域名提示 Temporary failure in name resolution错误。 rootlocalhost:~# ping www.baidu.com ping: www.baidu.com: Temporary failure in name resolution rootlocalhost:~# 一、ubuntu/debian(emporary failure i…

Docker容器:docker的资源控制及docker数据管理

文章目录 一.docker的资源控制1.CPU 资源控制1.1 资源控制工具1.2 cgroups有四大功能1.3 设置CPU使用率上限1.4 进行CPU压力测试1.5 设置50%的比例分配CPU使用时间上限1.6 设置CPU资源占用比(设置多个容器时才有效)1.6.1 两个容器测试cpu1.6.2 设置容器绑…

中间件(二)dubbo负载均衡介绍

一、负载均衡概述 支持轮询、随机、一致性hash和最小活跃数等。 1、轮询 ① sequences:内部的序列计数器 ② 服务器接口方法权重一样:(sequences1)%服务器的数量(决定调用)哪个服务器的服务。 ③ 服务器…

预警:传统的QA岗位将被DevOps淘汰

导读在大多数机构或公司里,软件开发过程主要遵循一个或多个开发模型,例如瀑布模型或敏捷模型。在瀑布模型中,测试活动一般都在后期进行。软件开发完成后,缺陷被QA团队找出,然后再被修复。后两个活动不断循环和重复&…

创建KVM虚拟机

文章目录 安装KVM虚拟机环境准备硬件虚拟化添加一块磁盘分区并格式化创建挂载目录并挂载分区上传镜像: virt-manager图形化安装下载virt-manager开始安装 virsh-install命令行安装安装组件使用virt-install安装 virsh管理虚拟机基本命令拓展命令 安装KVM虚拟机 环境…