Python中的垃圾回收机制

news2024/12/24 20:50:56

Python的垃圾回收主要以引用计数为主,分代回收为辅。

引用计数

在Python中,使用了引用计数这一技术实现内存管理。一个对象被创建完成后就有一个变量指向这个对象,那么就这个对象的引用计数为1,以后如果有其他变量指向这个对象,其引用计数也会相应增加,如果将一个变量不再执行这个对象,那么这个对象的引用计数减1。如果一个对象没有任何变量指向这个对象,也即引用计数为0,那么这个对象会被Python回收。

具体地,引用计数法的原理是每个对象维护一个ob_ref,用来记录当前对象被引用的次数,也就是来追踪到底有多少引用指向了这个对象,当发生以下四种情况的时候,该对象的引用计数器+1:

  1. 对象被创建,比如a=14

  1. 对象被引用,比如b=a

  1. 对象被作为参数,传到函数中,比如func(a)

  1. 对象作为一个元素,存储在容器中,比如List={a,”a”,”b”,2}

与上述情况相对应,当发生以下四种情况时,该对象的引用计数器-1:

  • 当该对象的别名被显式销毁时,比如del a

  • 当该对象的引别名被赋予新的对象,比如a=26

  • 一个对象离开它的作用域,例如func函数执行完毕时,函数里面的局部变量的引用计数器就会减一(但是全局变量不会)

  • 将该元素从容器中删除时,或者容器被销毁时。

当指向该对象的内存的引用计数器为0的时候,该内存将会被Python虚拟机销毁

引用计数示例代码如下:

class Person(object):
    def __init__(self,name):
        self.name = name

    def __del__(self):
        print('%s执行了del函数'%self.name)

while True:
    p1 = Person('p1')
    p2 = Person('p2')
    del p1
    del p2
    a = input('test:')

Python里面每一个东西都是对象,他们的核心是一个结构体Py_Object,所有Python对象的头部包含了这样一个结构PyObject

// object.h
struct _object {
    Py_ssize_t ob_refcnt;  # 引用计数值
    struct PyTypeObject *ob_type;
} PyObject;

看一个比较具体点的例子,int型对象的定义:

// intobject.h
typedef struct {
        PyObject_HEAD
        long ob_ival;
} PyIntObject;

简而言之,PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少。当引用计数为0时,该对象生命就结束了。

引用计数法有很明显的优点

  • 高效。

  • 运行期没有停顿。可以类比一下Ruby的垃圾回收机制,也就是实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

  • 对象有确定的生命周期。

  • 易于实现。

原始的引用计数法也有明显的缺点:

  • 维护引用计数消耗资源,维护引用计数的次数和引用赋值成正比。

  • 无法解决循环引用的问题。比如现在有两个对象分别为aba指向了bb又指向了a,那么他们两的引用计数永远都不会为0。也即永远得不到回收。

循环引用的示例:

class Person(object):
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print('%s执行了del函数' % self.name)


while True:
    p1 = Person('p1')
    p2 = Person('p2')
    p1.next = p2
    p2.prev = p1
    del p1
    del p2
    a = input('test:')

为了解决这两个致命弱点,Python又引入了以下两种GC机制。

标记清除

针对循环引用的情况:我们有一组未使用的、互相指向的对象,但是谁都没有外部引用。换句话说,我们的程序不再使用这些节点对象了,所以我们希望Python的垃圾回收机制能足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。Python的引用计数算法不能够处理互相指向自己的对象。你的代码也许会在不经意间包含循环引用并且你并未意识到。事实上,当你的Python程序运行的时候它将会建立一定数量的“浮点数垃圾”,Python的GC不能够处理未使用的对象因为应用计数值不会到零。这就是为什么Python要引入Generational GC算法的原因!

标记清除算法是一种基于追踪回收技术实现的垃圾回收算法。它分为两个阶段:

  • 第一阶段是标记阶段,GC会把所有的活动对象打上标记

  • 第二阶段是把那些没有标记的对象非活动对象进行回收。

那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。见下图:

在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。

标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

正如Ruby使用一个链表(free list)来持续追踪未使用的、自由的对象一样,Python使用一种不同的链表来持续追踪活跃的对象。而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)。在Python程序中,每次新创建了一个对象,那么就会将这个对象挂到一个叫做零代链表中。如果创建的对象总和减去被释放的对象,达到一定的值(某个阈值),那么Python就会遍历这个零代链表,找到那些有相互引用的对象,将这些对象的引用计数减1,如果引用计数值为0了,那么就说明这个对象是可以被释放的。接下来再将没有被释放的对象,挪动到一个新的链表中,这个链表叫做一代链表。

在零代链表清理的次数达到某个阈值后,Python会去遍历一代链表,将那些没有得到释放的对象移动到二代链表。同样的原理,如果一代链表清理的次数达到某个阈值后,Python会去遍历二代链表,把垃圾对象进行回收。

Python中的 GC 阈值

从某种意义上说,Python的GC算法类似于Ruby所用的标记回收算法。周期性地从一个对象到另一个对象追踪引用以确定对象是否还是活跃的,正在被程序所使用的,这正类似于Ruby的标记过程。

Python什么时候会进行这个标记过程?随着程序地运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。

但是事实并非如此。因为循环引用的原因,并且因为程序种中使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。

随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。

通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。

弱代假说

来看看分代垃圾回收算法的核心行为:垃圾回收器会更频繁的处理新对象。一个新的对象即是你的程序刚刚创建的,而一个来的对象则是经过了几个时间周期之后仍然存在的对象。Python会在当一个对象从零代移动到一代,或是从一代移动到二代的过程中提升这个对象。

这种算法的根源来自于弱代假说(weak generational hypothesis)。这个假说由两个观点构成:

  • 新的对象通常死得更快;

  • 老对象很有可能存活更长的时间。

假定现在我用Python或是Ruby创建一个新对象 n1=”ABC”,根据假说,我的代码很可能仅仅会使用ABC很短的时间。这个对象也许仅仅只是一个方法中的中间结果,并且随着方法的返回这个对象就将变成垃圾了。大部分的新对象都是如此般地很快变成垃圾。然而,偶尔程序会创建一些很重要的,存活时间比较长的对象,例如web应用中的session变量或是配置项。

通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才回去处理那些老变量。

分代回收

先给出gc的逻辑: 首先分配内存,一旦发现超过阈值了,则触发垃圾回收,然后将所有可收集对象链表放到一起进行遍历,计算有效引用计数,将其分成【有效引用计数=0】和【有效引用计数 > 0】两个集合,将大于0的,放入到更老一代,等于0的,执行回收。回收遍历容器内的各个元素,减掉对应元素引用计数(破掉循环引用),执行-1的逻辑,若发现对象引用计数等于0,触发内存回收,python底层内存管理机制回收内存。

Python中引入了分代收集,总共三个“代”。在Python 中,一个代就是一个链表,所有属于同一”代”的内存块都链接在同一个链表中。

Python默认定义了三代对象集合,索引数越大,对象存活时间越长。新生成的对象会被加入第0代,每新生成一个对象都会检查第0代有没有满,如果满了就开始着手进行垃圾回收。

简单来说,分代回收是一种以空间换时间的操作方式。

Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象。

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

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

相关文章

不吹牛,完爆ant design的定位组件,floating-ui来也

前言 因为要写react定位组件(这不是标题党,就是完爆ant design的定位组件,你应该看到一半就会同意我的观点),如下图: 红框部分是用绝对定位放在按钮上面的,你们B端用的主流组件库都是这样实现的…

Python自动化小技巧14——自动批量发送邮件(带各种附件)

案例背景 我的博客下面评论都是各种要数据的......一个一个发其实很浪费时间的,每次输入评论者的邮箱,然后打开数据所在的文件夹,上传,填写标题正文,发送....... 一模一样的流程,所以这种重复性的劳动肯定…

Linux下 git 上传与删除 的基本指令

git的概述克隆仓库使用 git 上传文件删除 git 中的文件git的概述 Git 是一个免费并开源的分布式版本控制系统,可以快速高效地处理从小型到大型的各种项目。 在使用 git 应确保Linux系统中已安装有git 命令:git --version 作用:查看 git 是否…

Spring Boot 单元测试

文章目录1. 单元测试是什么2. 单元测试的优点3. 进行 Spring Boot 单元测试3.1 确认项目中已经内置了测试框架3.2 生成单元测试的类3.3 添加 SpringBootTest 注解3.4 添加单元测试的业务代码3.5 注解 Transactional4. 断言1. 单元测试是什么 单元测试,是指对软件中…

微信小程序022同学会学生会活动经费系统

同学会小程序采用B/S结构、java开发语言、以及Mysql数据库等技术。系统主要分为管理员端和用户端两部分,管理员管理主要功能包括:首页、个人中心、用户管理、共享账本管理、我的账本管理、经费信息管理、经费支出管理、活动信息、管理员管理、留言板管理…

分享怎么做公众号预约_美容院预约小程序开发制作功能介绍

小程序的功能首先是为美人有约客户提供更快选购、预约服务的线上工具,解决顾客对商品详情、线上购买、线上预约查看等各种服务需求。一、美容美发预约下单小程序主要功能有:首页:搜索、banner、金刚区、瓷片区、项目列表预约:单次…

Java技术栈,从入门到放弃,废了废了

Java技术路线应用框架后端Spring家族SpringIoCAOPSpring MVCSpring Boot自动配置、开箱即用整合Web整合数据库(事务问题)整合权限ShiroSpring Security整合中间件缓存MQRPC框架NIO框架服务器软件应用服务器TomcatJettyUndertowWeb服务器Nginx中间件缓存R…

Deathstalker的核心武器——Janicab新变种

01 概述 DeathStalker是一个专门针对金融机构和律师事务所进行攻击的组织,而Janicab是其所使用的比较古老的武器。 Janicab 首次在2013年被发现,它是能够运行在MacOS和Windows操作系统上的恶意软件。其中,Windows版本基于VBscript的植入作为…

【第27天】SQL进阶-查询优化- performance_schema系列实战三:锁问题排查(表级锁)(SQL 小虚竹)

回城传送–》《32天SQL筑基》 文章目录零、前言一、什么是表级锁二、什么时候适合加表级锁三、实战演练3.1 数据准备(如果已有数据可跳过此操作)3.2 开启第一个会话,执行显式加表级锁3.3 开启第二个会话,对该表执行update更新3.4 …

Vue 3.0 应用组件实例

#创建一个应用实例 每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的: const app Vue.createApp({ /* 选项 */ }) 该应用实例是用来在应用中注册“全局”组件的。我们将在后面的指南中详细讨论,简单的例子: const app V…

jsp学生管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 jsp 学生管理系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开 发,数据库为Mysql,使用ja…

【Docker】基础使用

目录 一、Docker简介 二、Docker核心概念 三、Docker安装 四、Docker常用操作 1. 镜像操作 2. 容器操作 3. 安装MySQL 一、Docker简介 Docker 是一个开源的应用容器引擎,基于Go 语言并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖…

基于Springboot搭建java项目(二十二)——过滤器、监听器和拦截器的使用

过滤器、监听器和拦截器的使用 一、过滤器、监听器和拦截器总览 过滤器(Filter)监听器(Listener)拦截器(Interceptor)关注点web请求系统级别参数、对象Action(部分web请求)如何实现…

20230201在AIO-3568J开发板在原厂Android11下增加右键返回

20230201在AIO-3568J开发板在原厂Android11下增加右键返回 2023/2/1 8:37 百度搜索:RK3568 右键返回 Z:\rk3568_Android11.0_ap6257s\frameworks\native\services\inputflinger\reader\mapper\accumulator\CursorButtonAccumulator.cpp uint32_t CursorButtonAccumu…

【学习OpenCV4】如何学习OpenCV

OpenCV是应用非常广泛的开源视觉处理库,在图像处理、计算机视觉和自动驾驶中有着非常重要的作用。 废话不多说,我就来讲讲OpenCV的使用和学习需要怎么做吧,大家觉得有道理的可以参考一下。 我理解的学习和使用的重点在四个方面: …

PTA L1-019 谁先倒(详解)

前言:本期是关于谁先倒的详解,内容包括四大模块:题目,代码实现,大致思路,代码解读,今天你c了吗? 题目: 划拳是古老中国酒文化的一个有趣的组成部分。酒桌上两人划拳的方…

多模态搜索的未来:超越关键字和向量的混合搜索!

二十年前,“混合”一词仅在植物学和化学领域使用。如今,“混合”这个概念在搜索领域一片繁荣,许多搜索系统都在推出基于 AI 技术的混合搜索方案。但是,“混合搜索”是真的具有应用价值,还只是流行的一阵风呢&#xff1…

深度学习:Self-Attention与Multi-heads Attention详解

深度学习:Self-Attention与Multi-heads Attention详解IntroductionSelf - AttentionMulti-Head AttentionPosition- EncodingIntroduction Transformer 最初是由 Ashish Vaswani等人提出的一种用以完成机器翻译的 Seq2Seq 学习任务的全新网络结构,它完全…

nginx学习笔记9(小滴课堂)

业界主流高可用方案Linux虚拟服务器 LVS讲解 这部分因为需要多台虚拟机,所以我先不实操。 Keepalived核心配置讲解 echo "" > keepalived.conf是清空文件中的内容。 从机的优先级可以写的比master的优先级低一些。 准备NginxLvsKeepAlive相关软件环境 …

什么是最大子数组问题?

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注! 作者| 慕课网精英讲师 JdreamZhang 最大子数组(Max Subarray)问题,是计算机科学与技术领域中一种常见的算法问题&#…