python内存回收gc模块

news2025/1/15 17:55:29

目录

      • 1. python 垃圾回收机制
        • 标记-清除的回收机制
        • 分代回收
      • 2. gc 模块
      • 参考资料

对已经销毁的对象,Python不会自动释放其占据的内存空间。为了能够充分地利用分配的内存,避免程序跑到一半停止,要时不时地进行内存回收,这时候gc(garbage collector)就隆重登场啦!

用gc模块进行垃圾回收非常简单,如下所示:

# 加载gc模块
import gc

# 垃圾回收
# gc.collect() 返回处理这些循环引用一共释放掉的对象个数
gc.collect()

垃圾回收开始的时候当前所有线程都将被挂起,开始收集托管堆上的垃圾,收集完了还要压缩内存,然后等待垃圾回收结束以后再恢复这些线程,从这个角度来说,还是少调用垃圾回收,但是不是不能调,要视情况而定。 【占用内存较多】

将变量的引用删除后,进行内存回收。

意思是gc.collect()必须在手动 del 相关变量后,才能执行?? 而且也只是回收del处理的这些变量,而不会自动回收其他变量??

del 不是将内容删除,是将对应引用删除;如果没有删除引用,gc 不会回收。意思是我只要 del 了引用,gc 会自动将这些变量删除。

1. python 垃圾回收机制

Python中,主要依靠gc(garbage collector)模块的引用计数技术来进行垃圾回收。

所谓引用计数,就是考虑到Python中变量的本质不是内存中一块存储数据的区域,而是对一块内存数据区域的引用。所以python可以给所有的对象(内存中的区域)维护一个引用计数的属性,在一个引用被创建或复制的时候,让python,把相关对象的引用计数+1;相反当引用被销毁的时候就把相关对象的引用计数-1。当对象的引用计数减到0时,自然就可以认为整个python中不会再有变量引用这个对象,所以就可以把这个对象所占据的内存空间释放出来了。

引用计数技术在每次引用创建和销毁时都要多做一些操作,这可能是一个小缺点,当创建和销毁很频繁的时候难免带来一些效率上的不足。但是其最大的好处就是实时性,其他语言当中,垃圾回收可能只能在一些固定的时间点上进行,比如当内存分配失败的时候进行垃圾回收,而引用计数技术可以动态地进行内存的管理。

如果说效率只是一个不足的话,那么引用计数存在一些比较致命的软肋使得其一直不被接受为一种可以广泛运用的垃圾回收机制,这便是对循环引用的处理。在Python中有一些类型比如tuple,list,dict等,其作为容器类型可以包含若干个对象。如果某个对象就是它本身,或者两个对象中互相包含对方,那么就构成了一个循环引用。比如下面这段代码:

import sys

class Test():
  def __init__(self):
    pass

t = Test()
k = Test()
t._self = t
print(sys.getrefcount(t))   #sys.getrefcount函数用来查看一个对象有几个引用
print(sys.getrefcount(k))
####结果####
3
2

getrefcount函数查看一个对象存在几个引用关系,一般状态下的普通变量如上面的k,返回值都是2。不是1是因为把k作为参数传递给函数的时候,要先复制一份引用,然后把这个引用赋给形式参数供函数运行,在函数运行过程中,会保持这个引用始终升高为2。

从上面运行的结果可以看出来,Test 类实例 t 由于添加了一个自己对自己的引用,相当于:
在这里插入图片描述
这样总共 3 个引用。

del 语句可以消除一个引用关系。对于没有 _self 这样的自我引用的情况下,del(k) 相当于销毁了变量名到内存地址的这一层引用关系,自getrefcount执行完成之后,这部分内存就可以得到释放了。但是如果存在_self这个自我引用的话,即使消除了del(t)这个引用关系,这个对象的引用计数仍然是1。得不到销毁,所以会造成内存泄露

可以看到,基于引用计数的垃圾回收机制,因为循环引用的存在可能会导致内存泄露,所以python在引用计数的基础上,也增加了其他几种垃圾回收的方式。这里简单提一下。

标记-清除的回收机制

针对循环引用这个问题,比如有两个对象互相引用了对方,当外界没有对他们有任何引用,也就是说他们各自的引用计数都只有 1 的时候,如果可以识别出这个循环引用,把它们属于循环的计数减掉的话,就可以看到他们的真实引用计数了。

基于这样一种考虑,有一种方法,比如从对象A出发,沿着引用寻找到对象B,把对象B的引用计数减去1;然后沿着B对A的引用回到A,把A的引用计数减1,这样就可以把这层循环引用关系给去掉了。

不过这么做还有一个考虑不周的地方。假如A对B的引用是单向的, 在到达B之前我不知道B是否也引用了A,这样子先给B减1的话就会使得B称为不可达的对象了。

为了解决这个问题,python中常常把内存块一分为二,将一部分用于保存真的引用计数,另一部分拿来做为一个引用计数的副本,在这个副本上做一些实验。

比如在副本中维护两张链表,一张里面放不可被回收的对象合集,另一张里面放被标记为可以被回收(计数经过上面所说的操作减为0)的对象,然后再到后者中找一些被前者表中一些对象直接或间接单向引用的对象,把这些移动到前面的表里面。这样就可以让不应该被回收的对象不会被回收,应该被回收的对象都被回收了。

分代回收

分代回收策略着眼于提升垃圾回收的效率。研究表明,任何语言,任何环境的编程中,对于变量在内存中的创建/销毁,总有频繁和不那么频繁的。比如任何程序中总有生命周期是全局的、部分的变量。

而在垃圾回收的过程中,其实在进行垃圾回收之前还要进行一步垃圾检测,即检查某个对象是不是垃圾,该不该被回收。

当对象很多,垃圾检测将耗费大量的时间,而真的垃圾回收花不了多久。对于这种多对象程序,我们可以把一些进行垃圾回收频率相近的对象称为“同一代”的对象。垃圾检测的时候可以对频率较高的“代”多检测几次,反之,进行垃圾回收频率较低的“代”可以少检测几次。这样就可以提高垃圾回收的效率了。至于如何判断一个对象属于什么代,python中采取的方法是通过其生存时间来判断。如果在好几次垃圾检测中,该变量都是reachable的话,那就说明这个变量越不是垃圾,就要把这个变量往高的代移动,要减少对其进行垃圾检测的频率。

2. gc 模块

根据以上的介绍,我们知道了python对于垃圾回收,采取的是引用计数为主,标记-清除+分代回收为辅的回收策略。

对于循环引用的情况,一般的自动垃圾回收方式肯定是无效了,这时候就需要显式地调用一些操作来保证垃圾的回收和内存不泄露。这就要用到python内建的垃圾回收模块gc模块了。

最常见的 gc 模块的使用就是用 gc.collect() 方法。那就先来看下这个方法把:

import sys
import gc

a = [1]
b = [2]
a.append(b)
b.append(a)
####此时a和b之间存在循环引用####
sys.getrefcount(a)    #结果应该是3
sys.getrefcount(b)    #结果应该是3
del a
del b
####删除了变量名a,b到对象的引用,此时引用计数应该减为1,即只剩下互相引用了####
try:
    sys.getrefcount(a)
except UnboundLocalError:
     print('a is invalid')

>>> NameError: name 'a' is not defined
####此时,原来a指向的那个对象引用不为0,python不会自动回收它的内存空间####
####但是我们又没办法通过变量名a来引用它了,这就导致了内存泄露####
unreachable_count = gc.collect()
####gc.collect()专门用来处理这些循环引用,返回处理这些循环引用一共释放掉的对象个数。这里返回是2####

可以看到,没有gc模块的时候,我们对循环引用是束手无策的,在调用了一些gc模块的方法之后,它会实现上面“垃圾回收机制”部分中提到的一些策略比如“标记-清除”来进行垃圾回收。因为有了这个模块的封装,我们就不用关心具体的实现了。

然而collect方法也不是万能的。有些时候它并不能有效地回收所有该回收的对象。比如下面这样一段代码:

import sys
import gc

class A():
  def __init__(self):
    pass
  def __del__(self):
    pass

class B():
  def __init__(self):
    pass
  def __del__(self):
    pass

a = A()
b = B()
a._b = b
b._a = a
del a
del b

print(gc.collect())    #结果是4
print(gc.garbage)    #结果是[<__main__.A instance at 0x0000000002296448>, <__main__.B instance at 0x0000000002296488>]

可以看到,对我们自定义类的对象而言,collect方法并不能解决循环引用引起的内存泄露,即使在collect过后,解释器中仍然存在两个垃圾对象。

这里需要明确一下,之前对于“垃圾”二字的定义并不是很明确,在这里的这个语境下,垃圾是指在经过collect的垃圾回收之后仍然保持unreachable状态,即无法被回收,且无法被用户调用的对象应该叫做垃圾

gc模块中有garbage这个属性,其为一个列表,每一项都是当前解释器中存在的垃圾对象。一般情况下,这个属性始终保持为空集。

那么为什么在这种场景下collect不起作用了呢?

这主要是因为我们在类中重载了__del__方法。__del__方法指出了在用del语句删除对象时除了释放内存空间以外的操作。一般而言,在使用了del语句的时候解释器会首先看要删除对象的引用计数,如果为0,那么就释放内存并执行__del__方法。在这里,首先del语句出现时本身引用计数就不为0(因为有循环引用的存在),所以解释器不释放内存;

再者,执行collect方法时照理由应该会清除循环引用所产生的无效引用计数从而达到del的目的,对于这两个对象而言,python无法判断调用它们的__del__方法时会不会要用到对方那个对象,比如在进行b.del()时可能会用到b._a也就是a,如果在那之前a已经被释放,那么就彻底GG了。为了避免这种情况,collect方法默认不对重载了__del__方法的循环引用对象进行回收,而它们俩的状态也会从unreachable转变为uncollectable。由于是uncollectable的,自然就不会被collect处理,所以就进入了garbage列表。

collect返回4的原因是因为,在A和B类对象中还默认有一个__dict__属性,里面有所有属性的信息。比如对于a,有a.dict = {‘_b’:<main.B instance at xxxxxxxx>}。a的__dict__和b的__dict__也是循环引用的。但是字典类型不涉及自定义的__del__方法,所以可以被collect掉。所以garbage里只剩下两个了。

有时候garbage里也会出现那两个__dict__,这主要是因为在前面可能设置了gc模块的debug模式,比如

gc.set_debug(gc.DEBUG_LEAK),会把所有已经回收掉的unreachable的对象也都加入到garbage里面。set_debug还有很多参数诸如gc.DEBUG_STAT|DEBUG_COLLECTABLE|DEBUG_UNCOLLECTABLE|DEBUG_SAVEALL等等,设置了相关参数后gc模块会自动检测垃圾回收状况并给出实时地信息反映。

● gc.get_threshold()
  
  这个方法涉及到之前说过的分代回收的策略。python中默认把所有对象分成三代。第0代包含了最新的对象,第2代则是最早的一些对象。在一次垃圾回收中,所有未被回收的对象会被移到高一代的地方。

这个方法返回的是(700,10,10),这也是gc的默认值。这个值的意思是说,在第0代对象数量达到700个之前,不把未被回收的对象放入第一代;而在第一代对象数量达到10个之前也不把未被回收的对象移到第二代。可以是使用gc.set_threshold(threashold0,threshold1,threshold2)来手动设置这组阈值。

参考资料

[1] 使用gc.collect()进行垃圾回收 2022.5;
[2] 【Python】 垃圾回收机制和gc模块 2017.8;
[3] Python:gc模块使用和垃圾回收机制 2021.11;

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

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

相关文章

超详细——Python中 pip 常用命令

人生苦短&#xff0c;我学Python 相信对于大多数熟悉Python的人来说&#xff0c;一定都听说并且使用过pip这个工具&#xff0c;但是对它的了解可能还不一定是非常的透彻&#xff0c;今天小编就来为大家介绍10个使用pip的小技巧&#xff0c;相信对大家以后管理和使用Python当中…

每天一道大厂SQL题【Day19】华泰证券真题实战(一)

每天一道大厂SQL题【Day19】华泰证券真题实战(一) 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…

CSS中相对定位与绝对定位的区别及作用

CSS中相对定位与绝对定位的区别及作用场景复现核心干货相对定位绝对定位子绝父相&#x1f525;&#x1f525;定位总结绝对定位与相对定位的区别场景复现 在学习前端开发的过程中&#xff0c;熟练掌握页面布局和定位是非常重要的&#xff0c;因此近期计划出一个专栏&#xff0c…

【问题、AI解答】mongodb中使用$lookup进行连表查询使用_id作为localField出现查询结果字段为空的情况

描述&#xff1a; db.acticles.aggregate([ {$lookup&#xff1a;{from:"acticlesMaptags",localField:"_id",foreignField:"acticleid",as:"tagid"} } ])acticlesMaptags集合中的acticleid字段存在与acticles集合中的_id相匹配的数据…

1.15 从0开始学习Unity游戏开发--游戏UI

上一章中&#xff0c;我们剩下最后一个任务&#xff0c;需要支持鼠标控制准心来进行设计&#xff0c;那么准心本质上就是一个始终呈现在屏幕上的一个图片&#xff0c;你当然可以用一个3D物体来制作&#xff0c;之前讲解渲染概念的时候也提到过&#xff0c;我们的屏幕就是相机的…

传智健康_day3

本章对检查组管理进行开发 一.新增检查组 1.修改新增弹层可见属性&#xff0c;添加重置表单功能 2.动态刷新检查组包含的检查项信息 <tr v-for"c in tableData"> 使用for循环来遍历查询出tableData中的数据 tableData是一个数组对象&#xff0c;定义在VUE…

hadoop分布式安装

文章目录1. 将安装包hadoop-3.1.3.tar.gz上次至linux中2. 进行解压操作3. 修改目录名称4. 配置环境变量5. 远程传输5.1 scp远程传输6. 免密登录7. 集群规划8. 修改自定义配置文件8.1 hadoop-env.sh8.2 core-site.xml8.3 hdfs-site.xml8.4 mapred-site.xml8.5 yarn-site.xml8.6 …

ReactNative入门

React基本用法&#xff1a; react与js不同的点在于 react使用的是虚拟DOM js是真实DOM 作用&#xff1a;当有新的数据填充 可以复用之前的&#xff0c;而js需要整体重新渲染 创建虚拟DOM还可以使用jsx语法直接声明&#xff1a; 注意要用babel标签将jsx转化为js 但是建议采用j…

UNIX环境高级编程——进程环境

7.1 引言 本章主要讲解了进程的环境。 7.2 main函数 C程序总是从main函数开始执行&#xff0c;其函数原型为&#xff1a; int main(int argc, char *argv[]);argc是命令行参数的数目&#xff0c;argv是指向参数的各个指针所构成的数组&#xff1b;当内核执行C程序时&#x…

SpringBoot集成Kafka详解

一、使用idea创建SpringBoot项目 1.1 使用Spring Initializr创建一个SpringBoot程序 点击Next。 1.2 添加依赖 依赖说明&#xff1a; Lombok简化实体类开发。 Spring Web让项目集成web开发所有依赖&#xff0c;包括Spring MVC&#xff0c;内置tomcat等。 Spring for Apache…

HNU-操作系统OS-2023期中考试复习-刷题

往年期中卷极难获得&#xff0c;这里找了几套卷子。可以看看。 因为往年都是从第一周开始上课的&#xff0c;所以进度会快一点&#xff0c;这学期是从第四周开始上课的&#xff0c;所以进程会慢些&#xff0c;讲到第九章所以只考到第九章。 同样因为太忙了&#xff0c;答案找…

图像分类卷积神经网络模型综述

图像分类卷积神经网络模型综述遇到问题 图像分类&#xff1a;核心任务是从给定的分类集合中给图像分配一个标签任务。 输入&#xff1a;图片 输出&#xff1a;类别。 数据集MNIST数据集 MNIST数据集是用来识别手写数字&#xff0c;由0~9共10类别组成。 从MNIST数据集的SD-1和…

ctfshow web入门web119-124

1.web119 和118题类似&#xff0c;只不过是过滤了PATH 0可以用任何字符代替&#xff0c;比如A,{A},A,{0} KaTeX parse error: Expected }, got # at position 2: {#̲SHLVL}1&#xff0c;或者{##},${#?} {PHP_VERSION:~A}2,php版本为x.x.2时 ${#IFS}3(linux下是3&#xff0c;…

IntelliJ IDEA 2023.1正式发布,Maven项目大提速支持Apache Dubbo

你好&#xff0c;我是YourBatman&#xff1a;做爱做之事❣交配交之人。 &#x1f4da;前言 一年一个大版本&#xff0c;共计3个中型版本&#xff0c;北京时间2023年3月月29日终于迎来了IntelliJ IDEA今年的首个版本2023.1。老规矩&#xff0c;吃肉之前&#xff0c;可以先把这…

顺丰科技x腾讯安全iOA联合案例获云安全联盟CSA 2022安全革新奖

近年来&#xff0c;随着云计算、大数据、物联网等技术的加速创新和应用&#xff0c;一场数字化的变革开始席卷各行各业。远程办公、业务协同、分支互联等需求涌现&#xff0c;随之而来的还有更加复杂多元的高级网络攻击。在此背景下&#xff0c;传统的基于边界的网络安全防护理…

矩阵键盘+CH559制作国产USB矩阵键盘

矩阵键盘+CH559制作国产USB矩阵键盘 文章目录 矩阵键盘+CH559制作国产USB矩阵键盘为什么选择CH559作为主控芯片?如何实现该款矩阵USB键盘?如何将矩阵键盘的信号转化为USB键盘信号?原材料: 矩阵键盘(附带行列键码定义)CH559开发板将矩阵键盘的所有排线连接到单片机的GPIO引…

基于stm32mp157 linux开发板ARM裸机开发教程5:ARM微处理器指令系统(连载中)

前言&#xff1a; 目前针对ARM Cortex-A7裸机开发文档及视频进行了二次升级持续更新中&#xff0c;使其内容更加丰富&#xff0c;讲解更加细致&#xff0c;全文所使用的开发平台均为华清远见FS-MP1A开发板&#xff08;STM32MP157开发板&#xff09; 针对对FS-MP1A开发板&…

Python | Python的自我介绍(前世今生)

本文概要 本篇文章主要介绍Python这门语言的前世今生&#xff0c;适合刚入门的小白或者想了解Python历史的同学&#xff0c;文中描述很详细&#xff0c;具有一定的学习价值&#xff0c;感兴趣的小伙伴快来一起学习吧。 个人简介 ☀️大家好&#xff01;我是新人小白博主朦胧的…

【机器学习】样本不均衡(class-imbalance)——解决方案与问题思考

目录问题提出问题重述与再理解第一个问题&#xff1a;假如样本不均衡&#xff0c;哪种分类器的泛化性能较好&#xff1f;第二个问腿&#xff1a;在样本不均衡的情况下&#xff0c;如何获得更健壮的模型问题解决方法样本不均衡对机器学习模型会造成什么影响什么模型适合样本不均…

小黑今天上午着急忙慌实习公司楼下笔试,晚上准备和尚香疯狂星期四明天继续现场笔试的leetcode之旅:1091. 二进制矩阵中的最短路径

小黑代码1 class Solution:def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:# 一定无解的情况if grid[0][0] 1 or grid[-1][-1] 1:return -1# 矩阵长度n len(grid)# 起点即终点if n 1:return 1# 访问集合seen {(0, 0)}# 初始化队列q collections.…