详解Python是如何进行内存管理的

本文详细阐述了Python中的引用计数机制、垃圾回收策略,包括标记清除和分代回收,以及如何通过调整内存池和优化内存管理来提升性能。特别关注了循环引用和GIL对内存效率的影响。
摘要由CSDN通过智能技术生成

文章已获作者授权转载,版权归原作者所有,如有侵权,与本账号无关,可联系删除。 原文作者:敲代码敲到头发茂密

python采用的是引用计数机制为主,标记-清除分代收集(隔代回收)两种机制为辅的策略。

1 对象的引用计数机制

引用计数是一种垃圾回收机制,而且也是一种最直观、最简单的垃圾回收机制。当python的某个对象的引用计数为0时,该对象就成为要被回收的垃圾了。但是并不是立马就回收,等到gc触发的时候才开始回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了。图片

一个变量指向了内存地址,引用计数为1,两个变量同时指向了一个内存地址,引用计数为2。

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=data%3Aimage%2Fsvg%2Bxml%2C%253C%253Fxml%20version%3D’1.0’%20encoding%3D’UTF-8’%253F%253E%253Csvg%20width%3D’1px’%20height%3D’1px’%20viewBox%3D’0%200%201%201’%20version%3D’1.1’%20xmlns%3D’http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg’%20xmlns%3Axlink%3D’http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink’%253E%253Ctitle%253E%253C%2Ftitle%253E%253Cg%20stroke%3D’none’%20stroke-width%3D’1’%20fill%3D’none’%20fill-rule%3D’evenodd’%20fill-opacity%3D’0’%253E%253Cg%20transform%3D’translate(-249.000000%2C%20-126.000000&pos_id=img-wQg1nvaJ-1711367721300)’ fill=‘%23FFFFFF’%3E%3Crect x=‘249’ y=‘126’ width=‘1’ height=‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

为什么引用计数为2呢?🔥🔥🔥注意:

查看一个对象的引用计数: sys.getrefcount() 总是会比实际+1 ,因为sys.getrefcount() 也调用了它一次 .但是print(sys.getrefcount(b))在执行完毕后引用就删除了。

Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。

1.1 引用计数增加

(1)一个对象分配一个新名称

`**a=4553223**`

(2)将其放入一个容器中(如列表、元组或字典)

`**a=4553223
b=a
c=[a]
c.append(a)
print(sys.getrefcount(a))**`

1.2 引用计数减少

(1)使用del语句删除引用

del a

(2)引用超出作用域或被重新赋值

def test():
    b=667787
test()

注:函数执行完函数中的引用计数为0,可以进行回收。

2 垃圾回收

当一个对象的引用计数归零时,它将被垃圾回收机制处理掉。

当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。

举例:v1和v2互相引用,把v1和v2del

v1 = [1, 5, 6]
v2 = [6, 9, 2]
v1.append(v2)
v2.append(v1)

del v1
del v2

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=data%3Aimage%2Fsvg%2Bxml%2C%253C%253Fxml%20version%3D’1.0’%20encoding%3D’UTF-8’%253F%253E%253Csvg%20width%3D’1px’%20height%3D’1px’%20viewBox%3D’0%200%201%201’%20version%3D’1.1’%20xmlns%3D’http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg’%20xmlns%3Axlink%3D’http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink’%253E%253Ctitle%253E%253C%2Ftitle%253E%253Cg%20stroke%3D’none’%20stroke-width%3D’1’%20fill%3D’none’%20fill-rule%3D’evenodd’%20fill-opacity%3D’0’%253E%253Cg%20transform%3D’translate(-249.000000%2C%20-126.000000&pos_id=img-FbTOuX0w-1711367721300)’ fill=‘%23FFFFFF’%3E%3Crect x=‘249’ y=‘126’ width=‘1’ height=‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=data%3Aimage%2Fsvg%2Bxml%2C%253C%253Fxml%20version%3D’1.0’%20encoding%3D’UTF-8’%253F%253E%253Csvg%20width%3D’1px’%20height%3D’1px’%20viewBox%3D’0%200%201%201’%20version%3D’1.1’%20xmlns%3D’http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg’%20xmlns%3Axlink%3D’http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink’%253E%253Ctitle%253E%253C%2Ftitle%253E%253Cg%20stroke%3D’none’%20stroke-width%3D’1’%20fill%3D’none’%20fill-rule%3D’evenodd’%20fill-opacity%3D’0’%253E%253Cg%20transform%3D’translate(-249.000000%2C%20-126.000000&pos_id=img-TjgQ0uzh-1711367721300)’ fill=‘%23FFFFFF’%3E%3Crect x=‘249’ y=‘126’ width=‘1’ height=‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)注:v1和v2对象被干掉了,但是堆内存中有相互引用,引用计数位为1;可是没有变量去接收,这些内存地址程序员想用都不能用到,并且还占用内存。解决办法就是用标记清除。

  1. 栈内存:存放变量和内存地址;堆内存**:存放值;**
  2. 内存溢出:由于内存是有限的,当计算机内存中存在大量的相互(循环)引用计数时,会占用大部分的内存;当新的变量进来时,但是内存不够用了,所以不会去开辟新的内存;
  3. GC的效率:垃圾回收时,python不能进行其他的任务,频繁的垃圾回收将大大降低python的工作效率;原因是GIL的存在,当GC运行的时候,会抢python解释器锁,其他线程处于阻塞状态,会降低python的工作效率;以GC的回收不要那么频繁;几个小时GC会运行一次,一次运行不到1秒;太不影响其他线程的工作了;
  4. GC的启动:当python运行时,会记录其中分配对象和取消分配对象的次数。当俩者的差值高于某个阈值(默认为700)时,垃圾回收才会启动;
  5. GC触发:①主动调用gc.collect();②GC达到阈值时自动触发;③程序退出时(一次性全部退出)。

2.1 标记清除

当应用程序可用的内存空间被耗尽时,就会停止整个程序,然后进行两项工作,标记和清除。

标记清除算法是一种基于追踪回收 技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』(有引用的对象)打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。

2.2 分代回收

因为, 标记和清除的过程效率不高。清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。还有一个问题就是:什么时候扫描去检测循环引用?

为了解决上述的问题,python又引入了分代回收。分代回收解决了标记清楚时什么时候扫描的问题,并且将扫描的对象分成了3级,以及降低扫描的工作量,提高效率。存活时间越久的对象,越不可能在后面的程序中变成垃圾。

所有的新建对象都是0代对象,当某一代对象经历过垃圾回收,依然存活,那么他就被归入下一代对象了。垃圾回收启动时,一定会扫描所有的0代对象,如果0代经过一定次数垃圾回收,那么就启动对1代的扫描清理;当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描:

0代:0代中对象个数达到700个(阈值),扫描一次;

1代:0代扫描10次,则1代扫描1次;

2代:1代扫描10次,则2代扫描1次。

3 Python的内存池

Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。

对于Python对象,以下几种情况,都有其独立的私有内存池。(字符串的驻留机制);

  1. 字符串长度为0或者1;
  2. 符合标识符的字符串(只包含字符数字下划线);
  3. 字符串只在编译时进行驻留,而非运行时;
  4. [-5,256]之间的整数数字。

(1)举例1:[-5,256]之间的整数数字

值相同内存地址也相同:

>>> a=66
>>> b=66
>>> id(a)
2526627451280
>>> id(b)

值相同内存地址不同:

>>> c=399
>>> d=399
>>> id(c)
2526635259696
>>> id(d)
2526635259536

原理图如下:

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=data%3Aimage%2Fsvg%2Bxml%2C%253C%253Fxml%20version%3D’1.0’%20encoding%3D’UTF-8’%253F%253E%253Csvg%20width%3D’1px’%20height%3D’1px’%20viewBox%3D’0%200%201%201’%20version%3D’1.1’%20xmlns%3D’http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg’%20xmlns%3Axlink%3D’http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink’%253E%253Ctitle%253E%253C%2Ftitle%253E%253Cg%20stroke%3D’none’%20stroke-width%3D’1’%20fill%3D’none’%20fill-rule%3D’evenodd’%20fill-opacity%3D’0’%253E%253Cg%20transform%3D’translate(-249.000000%2C%20-126.000000&pos_id=img-uEixdSS6-1711367721300)’ fill=‘%23FFFFFF’%3E%3Crect x=‘249’ y=‘126’ width=‘1’ height=‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

(2)举例2:符合标识符的字符串(只包含字符数字下划线)

>>> a="qwe%"
>>> b="qwe%"
>>> a is b
False
>>> c="123kobe"
>>> d="123kobe"
>>> c is d
True

(3)举例3:字符串长度为0或者1

>>> e=""
>>> f=""
>>> e is f
True
1234
>>> e="!"
>>> f="!"
>>> e is f
True

(4)举例4:字符串只在编译时进行驻留,而非运行时

>>> a="kk"
>>> b="k"+"k"
>>> a is b
True
1234
>>> a="kobe"
>>> b="".join('kobe')
>>> a is b
False

4 怎么优化内存管理

(1)手动垃圾回收

先调用del a ; 再调用gc.collect()即可手动启动GC(嵌套的引用删除不了,因为引用计数为1)

(2)调高垃圾回收阈值

gc.set_threshold 设置垃圾回收阈值(收集频率)。将 threshold0 设为零会禁用回收。

gc.set_threshold(800,20,20)

(3)避免循环引用

原文链接:https://blog.csdn.net/YZL40514131/article/details/125884570

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值