1709 ltsb 内存占用_深挖Python的内存管理机制:垃圾回收机制

Python 程序在运行时,需要在内存中开辟出一块空间,用于存放运行时产生的临时变量,计算完成后,再将结果输出到永久性存储器中。但是当数据量过大,或者内存空间管理不善,就很容易出现内存溢出的情况,程序可能会被操作系统终止。

ba58006a0e617592d8b86927d16b891e.png

而对于服务器这种用于永不中断的系统来说,内存管理就显得更为重要了,不然很容易引发内存泄漏。

这里的内存泄漏是指程序本身没有设计好,导致程序未能释放已不再使用的内存,或者直接失去了对某段内存的控制,造成了内存的浪费。

dbd9f8e978e8f31291a88fe0ac3aacc6.png

Python 是通过什么机制来管理不会再用到的内存空间的呢?

Python引用计数机制

在学习 Python 的整个过程中,我们一直在强调,Python 中一切皆对象,也就是说,在 Python 中你用到的一切变量,本质上都是类对象。
那么,如何知道一个对象永远都不能再使用了呢?很简单,就是当这个对象的引用计数值为 0 时,说明这个对象永不再用,自然它就变成了垃圾,需要被回收。
举个例子:

import osimport psutil# 显示当前 python 程序占用的内存大小def show_memory_info(hint):    pid = os.getpid()    p = psutil.Process(pid)       info = p.memory_full_info()    memory = info.uss / 1024. / 1024    print('{} memory used: {} MB'.format(hint, memory))def func():    show_memory_info('initial')    a = [i for i in range(10000000)]    show_memory_info('after a created')func()show_memory_info('finished')

输出结果为:

initial memory used: 47.19140625 MB
after a created memory used: 433.91015625 MB
finished memory used: 48.109375 MB

注意,运行此程序之前,需安装 psutil 模块(获取系统信息的模块),可使用 pip 命令直接安装,执行命令为 $pip install psutil,如果遇到 Permission denied 安装失败,请加上 sudo 重试。
可以看到,当调用函数 func() 且列表 a 被创建之后,内存占用迅速增加到了 433 MB,而在函数调用结束后,内存则返回正常。这是因为,函数内部声明的列表 a 是局部变量,在函数返回后,局部变量的引用会注销掉,此时列表 a 所指代对象的引用计数为 0,Python 便会执行垃圾回收,因此之前占用的大量内存就又回来了。
明白了这个原理后,稍微修改上面的代码,如下所示:

def func():    show_memory_info('initial')    global a    a = [i for i in range(10000000)]    show_memory_info('after a created')func()show_memory_info('finished')

输出结果为:

initial memory used: 48.88671875 MB
after a created memory used: 433.94921875 MB
finished memory used: 433.94921875 MB

上面这段代码中,global a 表示将 a 声明为全局变量,则即使函数返回后,列表的引用依然存在,于是 a 对象就不会被当做垃圾回收掉,依然占用大量内存。

669a38a1be050372bc2cfec60ea4588d.png

同样,如果把生成的列表返回,然后在主程序中接收,那么引用依然存在,垃圾回收也不会被触发,大量内存仍然被占用着:

def func():    show_memory_info('initial')    a = [i for i in derange(10000000)]    show_memory_info('after a created')    return aa = func()show_memory_info('finished')

输出结果为:

initial memory used: 47.96484375 MB
after a created memory used: 434.515625 MB
finished memory used: 434.515625 MB


以上最常见的几种情况,下面由表及里,深入看一下 Python 内部的引用计数机制。先来分析一段代码:

import simport ysa = []# 两次引用,一次来自 a,一次来自 getrefcountprint(sys.getrefcount(a))def func(a):    # 四次引用,a,python 的函数调用栈,函数参数,和 getrefcount    print(sys.getrefcount(a))func(a)# 两次引用,一次来自 a,一次来自 getrefcount,函数 func 调用已经不存在print(sys.getrefcount(a))

输出结果为:

2
4
2

注意,sys.getrefcount() 函数用于查看一个变量的引用次数,不过别忘了,getrefcount 本身也会引入一次计数。

另一个要注意的是,在函数调用发生的时候,会产生额外的两次引用,一次来自函数栈,另一个是函数参数。

import sysa = []print(sys.getrefcount(a)) # 两次b = aprint(sys.getrefcount(a)) # 三次c = bd = be = cf = eg = dprint(sys.getrefcount(a)) # 八次

输出结果为:

2
3
8

分析一下这段代码,a、b、c、d、e、f、g 这些变量全部指代的是同一个对象,而 sys.getrefcount() 函数并不是统计一个指针,而是要统计一个对象被引用的次数,所以最后一共会有 8 次引用。
理解引用这个概念后,引用释放是一种非常自然和清晰的思想。相比 C 语言中需要使用 free 去手动释放内存,Python 的垃圾回收在这里可以说是省心省力了。

52b537eeea5d7721095b2f968ee0521e.png

如果想手动释放内存,应该怎么做呢?

方法同样很简单,只需要先调用 del a 来删除一个对象,然后强制调用 gc.collect() 即可手动启动垃圾回收。例如:

import gcshow_memory_info('initial')a = [i for i in range(10000000)]show_memory_info('after a created')del agc.collect()show_memory_info('finish')print(a)

输出结果为:

initial memory used: 48.1015625 MB
after a created memory used: 434.3828125 MB
finish memory used: 48.33203125 MB
NameError Traceback (most recent call last) in
11
12 show_memory_info('finish')
---> 13 print(a)
NameError: name 'a' is not defined

引用次数为 0 是垃圾回收启动的充要条件吗?还有没有其他可能性呢?
其实,引用计数是其中最简单的实现,引用计数并非充要条件,它只能算作充分非必要条件,至于其他的可能性,下面所讲的循环引用正是其中一种。

ac9fc695b0e1c56ac5da8598f1774cae.png

循环引用

首先思考一个问题,如果有两个对象,之间互相引用,且不再被别的对象所引用,那么它们应该被垃圾回收吗?
举个例子:

def func():    show_memory_info('initial')    a = [i for i in range(10000000)]    b = [i for i in range(10000000)]    show_memory_info('after a, b created')    a.append(b)    b.append(a)func()show_memory_info('finished')

输出结果为:

initial memory used: 47.984375 MB
after a, b created memory used: 822.73828125 MB
finished memory used: 821.73046875 MB

程序中,a 和 b 互相引用,并且作为局部变量在函数 func 调用结束后,a 和 b 这两个指针从程序意义上已经不存在,但从输出结果中看到,依然有内存占用,这是为什么呢?因为互相引用导致它们的引用数都不为 0。
试想一下,如果这段代码出现在生产环境中,哪怕 a 和 b 一开始占用的空间不是很大,但经过长时间运行后,Python 所占用的内存一定会变得越来越大,最终撑爆服务器,后果不堪设想。
有读者可能会说,互相引用还是很容易被发现的呀,问题不大。可是,更隐蔽的情况是出现一个引用环,在工程代码比较复杂的情况下,引用环真不一定能被轻易发现。那么应该怎么做呢?
事实上,Python 本身能够处理这种情况,前面刚刚讲过,可以显式调用 gc.collect() 来启动垃圾回收,例如:

import gcdef func():    show_memory_info('initial')    a = [i for i in range(10000000)]    b = [i for i in range(10000000)]    show_memory_info('after a, b created')    a.append(b)    b.append(a)func()gc.collect()show_memory_info('finished')

输出结果为:

initial memory used: 49.51171875 MB
after a, b created memory used: 824.1328125 MB
finished memory used: 49.98046875 MB


事实上,Python 使用标记清除(mark-sweep)算法和分代收集(generational),来启用针对循环引用的自动垃圾回收。
当然,每次都遍历全图,对于 Python 而言是一种巨大的性能浪费。所以,在 Python 的垃圾回收实现中,标记清除算法使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类对象才有可能产生循环引用)。
而分代收集算法,则是将 Python 中的所有对象分为三代。刚刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。
事实上,分代收集基于的思想是,新生的对象更有可能被垃圾回收,而存活更久的对象也有更高的概率继续存活。因此,通过这种做法,可以节约不少计算量,从而提高 Python 的性能。

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

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

相关文章

Python学习11 继承

面向对象三大特征 继承 Python多继承,默认从左到右依次继承,使用,隔开 默认继承object类 #继承了object类,可以省略 class m:def eat(self):print(m)class m2(object):def eat(self):print(m)继承的注意事项 class A(object):de…

qq浏览器网页翻译_iOS 14中直接在Safari浏览器中翻译网页的方法!

在 iOS 14 中,苹果推出了「翻译」的应用程序,可以实时翻译数种不同的语言。同时,Safari 浏览器也新增了翻译功能。借助新的网页翻译功能,Safari 会根据设备的首选语言列表自动检测书否需要翻译访问的网页内容,以便继续…

密码学专题 相关概念的解析 对称算法|算法的安全性|非对称算法存在的问题|单向散列函数|数字签名的弊端|密钥交换

对称加密算法 对称加密算法又可以分成流加密算法和块加密 算法。流加密算法又称为序列加密算法或序列密码,它每次只对明文中的单个位或单个字节 进行加密操作。这种算法的优点是能够实时进行数据传输和解密,缺点是抗攻击能力比 较弱。块加密算法又称为分…

java 交换两个数的值(临时变量,加减,异或)

临时变量 开发中一般使用此方式 加减 第二种写法: //3.使用加减d1d1d2;d2d1-d2;d1d1-d2;System.out.println("d1"d1"\td2"d2);异或(位运算符) 原理 ^异或:相同为0,不同为1 总代码 class ExchangeNumber {public st…

51单片机下载完程序后不亮_程序如何下载到单片机中?单片机常用的四种烧写程序方式介绍...

单片机是一种可编程控制器,搭好硬件电路后,可以利用程序实现很多非常复杂的逻辑功能,与纯硬件电路相比,简化了硬件外围的设计、方便了逻辑的设计、丰富了逻辑的输出。不同厂家的单片机需要不同编程IDE来实现编程。在学习单片机之前…

java 使用三元运算符和if-else获取两个和三个数中的最大值

三元运算符格式 三元运算符:获取两个数中最大值 三元运算符:获取三个数中最大值 三元运算符:总代码 //案例1:获取两个数中最大的数int num178;int num256;int max1(num1>num2)?num1:num2;System.out.println("max1"max1);//max178//案例2:…

密码学专题 鉴别协议|实际应用的混合协议

鉴别是指确定一个人的身份,即确定一个人是否是他所宣称的身份 基于口令的鉴别协议 口令是最常用和最常见的鉴别协议。当登录一台重要的计算机时,它会要求输入用户 名和密码,用户名代表你的身份,口令起鉴别作用,如果你…

如何用texstudio下载ctex_公众号素材库视频如何下载,用这种方法就可以哦

随着微信的不断崛起,它现在不仅仅是一个交流软件还包含着很多功能,其中里面就有各种微信公众号,我们可以在公众号上找到许多有用的视频,今天拓途数据就告诉大家公众号素材库视频如何下载吧。 如何下载微信公众号里的视频文件 复制…

java 进制转换 十进制转二,八,十六进制

十进制转其他进制 因为在java中位运算符是直接对数值的二进制数进行操作的 并且 写的是二进制数在输出和操作时是以十进制数的形式; 所以十进制转二,八,十六进制;就已经实现了二进制,八进制,十进制&#x…

密码学专题 密钥生成|分组加密的模式 ECB|CBC|CFB|OFB

密钥生成 如何安全地生成密钥。即如何生成可信任的密钥,保证用户得到的密钥是安全的, 生成密钥的机器或程序是可信的。如何生成安全的密钥。安全的密钥没有统一准确的定义,但一般来说是指密钥抗 猜测和抗穷举等针对密钥攻击的能力。涉及密钥…

excel高级筛选怎么用_Excel表格中高级筛选的优点以及常用方法介绍

在Excel表格中普通的数据筛选只能满足一些基本的筛选要求,这篇文章为朋友们总结一下高级筛选的优点以及常用方法。一.与普通筛选相比,高级筛选可以使用比较运算符。在下图中要筛选性别为男并且评分大于90的人员。如果使用普通筛选需要一个个的勾选成绩大…

STL源码剖析 第二次温习 细节审核

临时对象的产生 临时对象也叫做 无名对象,(使用pass by value的方式会引发copy的操作,于是产生一个临时的对象),造成效率的负担,但是可以可以制造一些临时对象在型别的后面 直接加上() 并可以指定初始数值,相当于调用…

java 彩票游戏

题目 Math.random()获取随机数 Math.random()返回的是一个[0.0,1.0)的doule类型的数 所以,获取0-9:(int)Math.random()*10–> [0,10) 获取0-10:(int)Math.random()*101–> [0,11) 获取10-99:(int)Math.random()9010–> […

python异常处理_Python入门 断言与异常处理

一、断言断言,可以理解为判断是否断开的预言。assert 表达式 , 描述表达式为我们的预期结果,当表达式的结果为False时,抛出 AssertionError 异常,如无异常捕获程序遇到异常时直接结束运行。反之,表达式结果为True 程序…

百度关键词排名查询源码_章丘百度霸屏总部,关键词排名腾沃云

上海保沃腾沃云为您详细解读URiIr章丘百度霸屏总部的相关知识与详情,做网站SEO优化文章内容优化效果已经十分明显。内容的量量关于网站的整体量量很重要。通过关键字阐发,陈某们晓得需要环绕关键字编写网页。使您的内容的重要根底是您对网页筹算描述的卖…

密码学专题 数据填充的方式|序列加密的方式

电子密码本模式和加密分组链接模式的分组算法都要求加密输入的分组是固定长度 的,但是大多数输入明文可能都不是分组长度的整数倍,也就是说,最后一个分组一般来 说是不足一个分组长度的。为了使分组加密算法能够正常工作,通常使用…

win10此电脑不见了_教程 | win10总提示“你要允许此应用对电脑的修改吗”,如何关闭?...

在使用电脑的时候,你是否曾遇到过这样的情况,点击打开软件的时候,系统总是提示我们“你要允许此应用对你的设备进行修改吗?”难道是软件中病毒了吗?,这其实上并没有什么用处,只是系统无法判断软…

密码学专题 加密模式的选择|传输数据加密

分组加密模式和序列加密模式。它们之间最大的区 别在于分组加密模式每次对一组数据进行加密运算处理,而序列加密模式则逐位对数据进 行加密运算处理。事实上,在实际设计应用的算法中,并没有那么严格的区别,它们有可能是相互结合的…

微信公众号数据2019_2019年9月原创公众号排行榜数据报告出炉

西瓜数据发布 2019 年 9 月原创公众号排行榜,分别从西瓜指数、原创文章占比、周期内公众号发文次数、发文篇数、总阅读数、头条平均阅读数等方面展示公众号数据。榜单说明1、涉及榜单本期月榜截取 ①财经、②教育、③娱乐、④科技、⑤体育、⑥游戏、⑦文化、⑧美食、…

密码学专题 OpenSSL专题

OpenSSL总体架构 软件包分为三个主要的功能部分:密码算法库 、 SSL协议库及应用程序 MacOS,MS,OS/2及 VMS这几个目录,包含了在不同的 平台编译时的环境变量配置文件,在安装编译完成之后,这几个目录就没有作…