Python3--垃圾回收机制

news2024/11/14 23:55:34

一、概述

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

二、3种回收方法介绍

1、引用计数法

1.引用计数法案例

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

        引用计数 是计算机编程语言中的一种 内存管理技术 ,它将资源被引用的次数保存起来,当引用次数变为 0 时就将资源释放。它管理的资源并不局限于内存,还可以是对象、磁盘空间等等。
        Python 也使用引用计数这种方式来管理内存,每个 Python 对象都包含一个公共头部,头部中的 ob_refcnt 字段便用于维护对象被引用次数。

        一个典型的 Python 对象结构如下:

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

>>> import sys
>>> f = 6.66
>>> sys.getrefcount(f)
2
'''
引用计数器赋值是1,结果输出的是2
原因:初始化引用计数器赋值是1,当把对象作为函数参数传递给sys模块时,
会产生一个临时的引用,当输出这个值的时候永远要比实际+1,但实际他还是1

sys.getrefcount()函数返回指定对象的引用计数。
引用计数是一种追踪Python对象生命周期的技术,它表示指向该对象的引用数量。
'''

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

>>> ff = f
>>> sys.getrefcount(f)
3

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

>>> l = [f]
>>> l      
[4.56]
>>> sys.getrefcount(f)
4

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

        随着 getrefcount 函数执行完毕并返回,它的栈帧对象将从调用链中解开并销毁,这时 float 对象的引用计数也跟着下降。因此,当一个对象作为参数传个函数后,它的引用计数将加一;当函数返回,局部名字空间销毁后,对象引用计数又减一。
        引用计数就这样随着引用关系的变动,不断变化着。当所有引用都消除后,引用计数就降为零,这时 Python 就可以安全地销毁对象,回收内存了:

>>> del l
>>> del ff
>>> del f 
>>> sys.getrefcount(f)

2.引用计数增加

  1. 对象被创建
  2. 如果有新的对象使用该对象
  3. 作为容器对象的一个元素
  4. 被作为参数传递给函数

3.引用计数减少

  1. 对象的引用被显示的销毁
  2. 新对象不在使用该对象
  3. 对象从列表中被移除,或者列表对象本身被销毁
  4. 函数调用结束

4.引用机制优点

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

5.引用机制缺点

  • 维护引用计数消耗资源
  • 循环引用的问题无法解决
a = [1,2]
b = [3,4]
a.append(b) #b的计数器2
b.append(a) #a的计数器2
del a
del b

2、标记-清除法

1. 对比引用计数法

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

2. 循环引用

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

class People:
pass
class Cat:
pass
#创建People的实例对象
p = People()
#创建Cat的实例对象
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 )的垃圾对象,可以被安全回收。循环引用的致命缺陷完美解决了!

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

3. 【示例】标记清除法

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

3、分代回收机制

1. 分代回收机制

        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)

 2. 第一代链表

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

3.第二代链表

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

4.第三代链表

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

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

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

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

相关文章

「线性DP-学习案例」传球游戏

传球游戏 题目描述 ​ 上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。 ​ 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球…

Systemverilog中Assertions的记录

1. assertion statement Assertion statement有以下几种类型: assert: 指定DUT的property,必须要verifyassume: 给验证环境指定假设的property。simulator检查这些property,但是formal工具会使用这些信息来产生输入激励。cover: 监控proper…

面试题:Ajax、Fetch、Axios三者的区别

Ajax 它的全称是:Asynchronous JavaScript And XML,翻译过来就是“异步的 Javascript 和 XML”。 Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术, Ajax 是一种思想,X…

【Hello Network】网络编程套接字(一)

作者:小萌新 专栏:网络 作者简介:大二学生 希望能和大家一起进步 本篇博客简介:简单介绍网络的基础概念 网络编程套接字(一)预备知识源ip和目的ip端口号TCP和UDP协议网络中的字节序socket编程接口socket常见…

爬虫1000+个C程序

爬虫1000个C程序 问题场景 由于实验需要,我需要1000个elf文件,可是网络可获取的elf文件较少,c程序较多,所以首先下载c程序,之后gcc编译链接生成elf文件。我需要的C源码不是项目级别的,正常100行左右就可以…

PNAS:土地利用和土地覆盖的变化决定了保护区的可持续性和影响

PNAS 中文题目: 土地利用和土地覆盖的变化决定了保护区的可持续性和影响 英文题目: Land-use and land-cover change shape the sustainability and impacts of protected areas 作者: Determinants and impacts of protected area remova…

MATLAB 神经网络变量筛选—基于BP的神经网络变量筛选(链接在文末)

灰色系统理论是一种研究少数据、贫信息、不确定性问题的新方法,它以部分信息已知,部分信息未知的“小样本”,“贫信息”不确定系统为研究对象,通过对“部分”已知信息的生成、开发,提取有价值的信息,实现对…

软考第六章 网络互连与互联网

网络互连与互联网 1.网络互连设备 组成因特网的各个网络叫做子网,用于连接子网的设备叫做中间系统。它的主要作用是协调各个网络的工作,使得跨网络的通信得以实现。 网络互连设备可以根据它们工作的协议层进行分类: 中继器:工…

双周赛102(模拟、BFS技巧、求最短路模板问题)

文章目录双周赛102[6333. 查询网格图中每一列的宽度](https://leetcode.cn/problems/find-the-width-of-columns-of-a-grid/)模拟[6334. 一个数组所有前缀的分数](https://leetcode.cn/problems/find-the-score-of-all-prefixes-of-an-array/)模拟(一次遍历)😭[6335…

【黑马】JavaWeb开发教程(涵盖Spring+MyBatis+SpringMVC+SpringBoot等)目录合集

​Java Web 传统路线: 课程讲述路线: 视频链接: 2023新版JavaWeb开发教程,实现javaweb企业开发全流程 学习时间: 断断续续,按照课程安排正常学习,历时15天,完结撒花!…

快速认识并上手Eureka注册中心

文章目录一、初识Eureka1.1 EurekaServer1.2 EurekaClient1.2.1 EurekaClient中的角色二、EurekaServer2.1 搭建EurekaServer2.1.1 依赖引入2.1.2 添加注解2.1.3 配置eureka地址2.1.4 验证2.2 注册EurekaClient2.2.1 引入客户端依赖2.2.2 配置eureka地址2.2.3 验证2.3 服务发现…

【C++】多态---下(多态的原理)

前言: 在多态---上中我们了解了什么是多态,以及多态的使用条件等。本章将进行更深入的学习,我们详细理解多态的原理。 目录 (一)虚函数表 (1)虚函数表的引入 (2)虚表…

RHCE——时间服务器(ntp)

1.配置ntp时间服务器,确保客户端主机能和服务主机同步时间 2.配置ssh免密登陆,能够通过客户端主机通过redhat用户和服务端主机基于公钥验证方式进行远程连接 一.配置ntp时间服务器,确保客户端主机能和服务主机同步时间 1、软件安装 [rootl…

宝可梦朱紫太晶化效果小记

首先,不得不吐槽一下,switch上这么多代宝可梦下来,好玩是好玩,但是整体效果和优化不能说糟烂,只能说稀碎。 看这个朱紫的截帧都给我看吐了,上点心啊老任 回到效果,首先是实现方式 主要有俩点 …

3.Earth Engine语法Javascript版(基本属性2)

1.地图MAp 1. Map.add(item)这个方法通常是在地图展示区加入各种ui使用,如ui.Label 2.Map.centerObject(object, zoom)设置地图居中位置,参数object是矢量数据或者影响数据;zoom是缩放级别。 3.Map.addLayer(ee.Object, visParams, name, …

树莓派利用python-opencv使用CSI摄像头调用监控视频

目录 一、安装python-opencv。 二、使用工具Xshell7和MobaXterm 三、连接并打开CSI摄像头 3.1连线如图所示: 3.2打开摄像头 四、编写摄像头代码调用摄像头 一、安装python-opencv。 一定要选择配置好的安装python-opencv,不要去配置安装&#xff0c…

012 - C++指针

本期我们将学习 C 中的指针。 指针是一个令很多人都很痛苦的内容,然而指针其实没有大家想象中的那么复杂。另外我先要说明本期我们要讨论的是原始的指针,还有一种常用的指针叫智能指针,这个我们在之后的内容中会接触学习。 计算机处理内存&…

LeetCode_二叉搜索树_中等_236.二叉搜索树的最近公共祖先

目录1.题目2.思路3.代码实现(Java)1.题目 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 …

jQuery讲解|这一章就够了|(超详细|保姆级)

🙈作者简介:练习时长两年半的Java up主 🙉个人主页:老茶icon 🙊 ps:点赞👍是免费的,却可以让写博客的作者开兴好久好久😎 📚系列专栏:Java全栈,计…

【设计模式】生产者消费者模型

带你轻松理解生产者消费者模型!生产者消费者模型可以说是同步与互斥最典型的应用场景了!文末附有模型简单实现的代码,若有疑问可私信一起讨论。 文章目录一:为什么要使用生产者消费者模型?二:生产者消费者模…