a byte of python中文版_面试官问 Python 版 “垃圾回收”机制,我没答上来

点击“开发者技术前线”,选择“星标?”

13:21 在看|星标|留言,  真爱

2658848e-ec20-eb11-8da9-e4434bdf6706.jpeg选自《萌萌哒的柯基》 作者:heroyfhttps://www.heroyf.club/2019/10/23/python_gc/

前言

对于python来说,一切皆为对象,所有的变量赋值都遵循着对象引用机制。程序在运行的时候,需要在内存中开辟出一块空间,用于存放运行时产生的临时变量;计算完成后,再将结果输出到永久性存储器中。如果数据量过大,内存空间管理不善就很容易出现 OOM(out of memory),俗称爆内存,程序可能被操作系统中止。而对于服务器,内存管理则显得更为重要,不然很容易引发内存泄漏 - 这里的泄漏,并不是说你的内存出现了信息安全问题,被恶意程序利用了,而是指程序本身没有设计好,导致程序未能释放已不再使用的内存。- 内存泄漏也不是指你的内存在物理上消失了,而是意味着代码在分配了某段内存后,因为设计错误,失去了对这段内存的控制,从而造成了内存的浪费。也就是这块内存脱离了gc的控制

计数引用

因为python中一切皆为对象,你所看到的一切变量,本质上都是对象的一个指针。当一个对象不再调用的时候,也就是当这个对象的引用计数(指针数)为 0 的时候,说明这个对象永不可达,自然它也就成为了垃圾,需要被回收。可以简单的理解为没有任何变量再指向它。

import os

import 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

可以看到调用函数 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 声明为全局变量。那么,即使函数返回后,列表的引用依然存在,于是对象就不会被垃圾回收掉,依然占用大量内存。同样,如果我们把生成的列表返回,然后在主程序中接收,那么引用依然存在,垃圾回收就不会被触发,大量内存仍然被占用着:

def func():

show_memory_info('initial')

a = [i for i in derange(10000000)]

show_memory_info('after a created')

return a

a = 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

那怎么可以看到变量被引用了多少次呢?通过 sys.getrefcount

import sys

a = []

# 两次引用,一次来自 a,一次来自 getrefcount

print(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

如果其中涉及函数调用,会额外增加两次 1. 函数栈 2. 函数调用从这里就可以看到python不再需要像C那种的认为的释放内存,但是python同样给我们提供了手动释放内存的方法 gc.collect()

import gc

show_memory_info('initial')

a = [i for i in range(10000000)]

show_memory_info('after a created')

del a

gc.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)

<ipython-input-12-153e15063d8a> in

11

12 show_memory_info('finish')

---> 13 print(a)

NameError: name 'a' is not defined

截止目前,貌似python的垃圾回收机制非常的简单,只要对象引用次数为0,必定为触发gc,那么引用次数为0是否是触发gc的充要条件呢?

循环回收

如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?

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就已经从程序意义上不存在了。但是因为它们的互相引用,导致了它们的引用数都不为0。这时要如何规避呢 1. 从代码逻辑上进行整改,避免这种循环引用 2. 通过人工回收

import gc

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()

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针对循环引用,有它的自动垃圾回收算法 1. 标记清除(mark-sweep)算法 2. 分代收集(generational)

标记清除

标记清除的步骤总结为如下步骤 1. GC会把所有的『活动对象』打上标记 2. 把那些没有标记的对象『非活动对象』进行回收那么python如何判断何为非活动对象?通过用图论来理解不可达的概念。对于一个有向图,如果从一个节点出发进行遍历,并标记其经过的所有节点;那么,在遍历结束后,所有没有被标记的节点,我们就称之为不可达节点。显而易见,这些节点的存在是没有任何意义的,自然的,我们就需要对它们进行垃圾回收。但是每次都遍历全图,对于 Python 而言是一种巨大的性能浪费。所以,在 Python 的垃圾回收实现中,mark-sweep 使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类对象,list、dict、tuple,instance,才有可能产生循环引用)。2858848e-ec20-eb11-8da9-e4434bdf6706.png图中把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。

分代回收

分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时(当垃圾回收器中新增对象减去删除对象达到相应的阈值时),Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。事实上,分代回收基于的思想是,新生的对象更有可能被垃圾回收,而存活更久的对象也有更高的概率继续存活。因此,通过这种做法,可以节约不少计算量,从而提高 Python 的性能。所以对于刚刚的问题,引用计数只是触发gc的一个充分非必要条件,循环引用同样也会触发。

调试

可以使用 objgraph来调试程序,因为目前它的官方文档,还没有细读,只能把文档放在这供大家参阅啦~其中两个函数非常有用 1. show_refs() 2. show_backrefs()

总结

  1. 垃圾回收是 Python 自带的机制,用于自动释放不会再用到的内存空间;

  2. 引用计数是其中最简单的实现,不过切记,这只是充分非必要条件,因为循环引用需要通过不可达判定,来确定是否可以回收;

  3. Python 的自动回收算法包括标记清除和分代回收,主要针对的是循环引用的垃圾收集;

  4. 调试内存泄漏方面, objgraph 是很好的可视化分析工具。

END
开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。
历史推荐区块链入门教程,这一篇就足够了苹果 CEO:一半员工没大学学历,乔布斯不就是吗这个“忽悠”马云10 亿的男子,还了阿里 5000 亿,今天当选为院士!2b58848e-ec20-eb11-8da9-e4434bdf6706.png2d58848e-ec20-eb11-8da9-e4434bdf6706.gif3158848e-ec20-eb11-8da9-e4434bdf6706.png好文点个在看吧!2d58848e-ec20-eb11-8da9-e4434bdf6706.gif

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

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

相关文章

漫谈 Linux,Windows 和 Mac

好了&#xff0c;现在来一点技术性的。这段时间收到很多人的来信&#xff08;大部分自称是菜鸟&#xff09;。他们看了我很早以前写的推崇 Linux 的文章&#xff0c;想知道如何“抛弃 Windows&#xff0c;学习 Linux”。天知道他们在哪里找到那么老的文章&#xff0c;真是好事不…

AspNet2.0页面生命周期

AspNet2.0页面生命周期 页面框架通过如下过程处理aspx文件请求: 1&#xff1a;解析aspx文件,并创建一个控件树&#xff1b; 2&#xff1a;使用控件树动态实现一个继承自Page类的类或者控件 &#xff1b; 3&#xff1a;动态编译类&#xff1b; 4&#xff1a;缓存编…

数字时钟设计verilog_数字IC设计基本概念之创建时钟

来自微信公众号 “数字芯片实验室”时序分析的一个重要部分是准确地指定时钟和相关属性&#xff0c;例如延迟&#xff08;latency&#xff09;和不确定性&#xff08;uncertainty&#xff09;。 EDA工具可以分析以下类型的时钟信息&#xff1a;时钟网络latency和 skew&#xff…

软件开发模式对比(瀑布、迭代、螺旋、敏捷)

1、瀑布模型是由W.W.Royce在1970年最初提出的软件开发模型&#xff0c; 瀑布式开发是一种老旧的计算机软件开发方法。 瀑布模型式是最典型的预见性的方法&#xff0c;严格遵循预先计划的需求分析、设计、编码、集成、测试、维护的步骤顺序进行。 步骤成果作为衡量进度的方法&a…

photoshop script

http://www.adobe.com/devnet/photoshop/scripting/转载于:https://www.cnblogs.com/275095923/archive/2010/08/23/1806871.html

敏捷开发流程的8个步骤_敏捷开发——个体和互动高于流程和工具

敏捷开发是软件公司主流的项目管理方法&#xff0c;敏捷方法论有许多种&#xff0c;包括Scrum、极限编程(XP)以及精益(Lean)方法&#xff0c;但是它们都具有一个共同点&#xff1a;遵循敏捷宣言和敏捷原则。透明性。每一个敏捷项目成员都知道即将做什么以及项目进展如何。经常性…

瀑布开发

提到“瀑布开发”的时候&#xff0c;大部分人们可能会联想到尼亚加拉瀑布下要进行房地产开发&#xff0c;然后&#xff0c;设想一下&#xff0c;当您告诉他们实际上瀑布开发是一种包含多个阶段的反复叠代的软件开发模型时&#xff0c;他们会多么惊讶。这篇文章将为您提供一份关…

ArcGis Desktop10 注册机授权方法与安装步骤

今天&#xff0c;由于需要处理一些空间数据&#xff0c;安装ArcGIS Desktop10&#xff0c;所以就把注册机的授权方法与安装过程&#xff0c;写成Blog与大家一起分享&#xff01; 第一步&#xff1a;安装 ArcGIS许可管理器。如图所示&#xff1a; 第二步&#xff1a;打开注册机&…

【转】在C#中使用SQLite

SQLite 是一个嵌入式的关系数据库系统&#xff0c;使用十分广泛。在一些数据量不大的应用程序中&#xff0c;如果使用SQLite可以极大的减少部署时的工作量。 要在C#中使用SQLite也很简单&#xff0c;只要找一个C#的wrapper就可以了&#xff0c;例如&#xff0c;我使用的就是来自…

软件开发模式有哪些

软件开发模式有哪些? 快速原型模型&#xff1a;&#xff08;需要迅速造一个可以运行的软件原型&#xff0c;以便理解和澄清问题&#xff09; 快速原型模型允许在需求分析阶段对软件的需求进行初步的非完全的分析和定义&#xff0c;快速设计开发出软件系统的原型&#xff08;…

利用ArcGIS将经纬度数据转化成平面坐标数据

经度是指某点与两极的连线与0度经线所在平面的夹角&#xff0c;国际上规定以通过英国伦敦近郊的格林尼治天文台旧址的经线作为计算经度的起点&#xff0c;即经度零度零分零秒&#xff0c;也称“本初子午线”。它东面的为东经&#xff0c;记为E&#xff0c;共180度&#xff0c;西…

电脑连接电视方法详解_笔记本连接电视方法有哪些?分享两种笔记本连接电视方法...

在如今这个快节奏的时代中,大多数年轻人的手中都会有个笔记本电脑,并且将笔记本电脑作为休闲娱乐或办公的一种设备。相比于电视屏幕尺寸,笔记本电脑的屏幕还真是小了很多。为了提高视觉上的体验,很多小伙伴会选择笔记本连接电视的方式,将笔记本中的内容转换到电视屏幕上。下面我…

美国团购网站Groupon的盈利模式

GroupOn 一种模式&#xff0c;一个奇迹。用户每天疯狂在网站上抢购推出的餐饮、SPA、运动、游戏这样的商品或服务机会。 很难相信&#xff0c;这个网站在2008年11月份上线&#xff0c;在7个月后就实现了盈亏平衡&#xff0c;利润更是高达了30%以上。据报道&#xff0c;2010年销…

完整的连接器设计手册_减速齿轮箱的设计 用一整套完整流程来说明(附PDF手册)...

减速箱减速齿轮箱设计手册获取见文章末尾图片在通用的产品设计中&#xff0c;齿轮减速箱的设计是最简单成熟的一种设计&#xff0c;因为设计流程十分清晰&#xff0c;这已经是一种很成熟的产品了&#xff0c;下面我就来说一说如何做减速齿轮箱的设计&#xff0c;以及设计的一般…

WAMP Server 无法启动的解决方法

今天安装WAMP Server用来发布PHPMySQL做的网站&#xff0c;安装之后发现Apache服务无法启动&#xff0c;尝试用如下的方法进行解决。 首先利用WAMP自带的 Apache->Service->Test Port 80 功能&#xff0c;检测80端口是否被其他软件占用。 检测结果显示&#xff1a; You…

在 WinCe 平台读写 ini 文件

在上篇文章开发 windows mobile 上的今日插件时&#xff0c;我发现 wince 平台上不支持例如 GetPrivateProfileString 等相关 API 函数。在网络上我并没有找到令我满意的相应代码&#xff0c;因此我手工自己写了相应的方法。命名规则是&#xff0c;在 PC API 函数的名称前面加上…

SQL Server 兼容模式

近期一个老系统改用SQL Server 2005数据库, 从原来的2000改为2005, 默认情况下启动出错&#xff1a; Incorrect syntax near Index. If this is intended as a part of a table hint, A WITH keyword and parenthesis are now required. SQL 语句中的table hint 中缺少with 关键…

孩子学python用什么教材比较好-python大学里用哪本教材比较好?

看这本 英文好的话&#xff0c;不需要买书&#xff0c;看该书的英文电子版&#xff0c;免费的。 《How to Think Like a Computer Scientist- Learning with Python 3》 下载地址&#xff1a;https://github.com/pythonpeixun/article/blob/master/books/How%20to%20Think%20Li…

通过VisualSVN的POST-COMMIT钩子自动部署代码

这段时间我们一直规划LSGO Group的学习网络平台&#xff0c;需求部分已经规划完毕&#xff0c;说做就做&#xff0c;开始搭建环境&#xff0c;由于利用PHPMYSQL技术&#xff0c;在服务器端首先安装了WAMPServer&#xff0c;以便提供Apache服务与MYSQL服务&#xff01; 在代码的…

Linux内核的中断机制

5&#xff0e;1 I386的中断与异常 中断通常被分为“同步中断”和异步中断两大类。同步中断是指当指令执行时由CPU控制单元产生的中断&#xff0c;之所以称为“同步中断”是因为只有在一条指令中止执行后CPU才会发出这类中断信号。而异步中断则是指由其他硬件设备依照CPU时钟随…