Python 内存管理的工作原理你了解吗?

news/2024/4/29 1:54:41/文章来源:https://blog.csdn.net/m0_72557783/article/details/126994254

Python 为开发者提供了许多便利,其中最大的便利之一是其几乎无忧的内存管理。开发者无需手动为 Python 中的对象和数据结构分配、跟踪和释放内存。运行时会为你完成所有这些工作,因此你可以专注于解决实际问题,而不是争论机器级细节。

尽管如此,即使是经验不多的 Python 用户,了解 Python 的垃圾收集和内存管理是如何工作的也是有好处的。了解这些机制将帮助你避免更复杂的项目可能出现的性能问题。你还可以使用 Python 的内置工具来监控程序的内存管理行为。

Python如何管理内存

每个 Python 对象都有一个引用计数,也称为引用计数。 refcount 是持有对给定对象的引用的其他对象总数的计数。当你添加或删除对对象的引用时,数字会上升或下降。当一个对象的引用计数变为零时,该对象将被释放并释放其内存。

什么是参考?允许通过名称或通过另一个对象中的访问器访问对象的任何内容。

这是一个简单的例子:

x = "Hello there"

当我们向 Python 发出这个命令时,引擎盖下会发生两件事:

  1. 该字符串"Hello there"作为 Python 对象创建并存储在内存中。
  2. 该名称x在本地命名空间中创建并指向该对象,这会将其引用计数增加 1 到 1。

如果我们说y = x,那么引用计数将再次提高到 2。

每当xandy超出范围或从它们的命名空间中删除时,对于每个名称,字符串的引用计数都会减少 1。一旦x和y都超出范围或被删除,字符串的引用计数变为 0 并被删除。

现在,假设我们创建了一个包含字符串的列表,如下所示:

x = ["Hello there", 2, False]

字符串保留在内存中,直到列表本身被删除或包含字符串的元素从列表中删除。这些操作中的任何一个都将导致唯一持有对字符串的引用的事物消失。

现在考虑这个例子:

x = "Hello there" y = [x]

如果我们从 中删除第一个元素y,或者完全删除列表y,则字符串仍在内存中。这是因为名称x包含对它的引用。

Python 中的引用循环

在大多数情况下,引用计数工作正常。但有时你会遇到两个对象各自持有对彼此的引用的情况。这称为 参考周期。在这种情况下,对象的引用计数永远不会达到零,也永远不会从内存中删除。

这是一个人为的例子:

x = SomeClass()
y = SomeOtherClass()
x.item = y
y.item = x

由于x并y持有彼此的引用,因此它们永远不会从系统中删除——即使没有其他任何东西引用它们中的任何一个。

Python 自己的运行时为对象生成引用循环实际上是相当普遍的。一个示例是带有包含对异常本身的引用的回溯对象的异常。

在Python的早期版本中,这是一个问题。具有引用周期的对象可能会随着时间的推移而累积,这对于长时间运行的应用程序来说是一个大问题。但 Python 此后引入了循环检测和垃圾收集系统,用于管理引用循环。

Python 垃圾收集器 (gc)

Python 的垃圾收集器检测具有引用周期的对象。它通过跟踪作为“容器”的对象(例如列表、字典、自定义类实例)并确定其中的哪些对象无法在其他任何地方访问来实现这一点。

一旦这些对象被挑选出来,垃圾收集器就会通过确保它们的引用计数可以安全地降为零来删除它们。

绝大多数 Python 对象没有引用周期,因此垃圾收集器不需要 24/7 运行。相反,垃圾收集器使用一些启发式方法来减少运行频率,并且每次都尽可能高效地运行。

当 Python 解释器启动时,它会跟踪已分配但未释放的对象数量。绝大多数 Python 对象的生命周期都很短,因此它们会迅速出现和消失。但随着时间的推移,更多长寿的物体会出现。一旦超过一定数量的此类对象堆积起来,垃圾收集器就会运行。

每次垃圾收集器运行时,它都会收集所有在收集中幸存下来的对象,并将它们放在一个称为一代的组中。这些“第一代”对象在参考周期中被扫描的频率较低。任何在垃圾收集器中幸存下来的第一代对象最终都会迁移到第二代,在那里它们被扫描得更少。

同样,垃圾收集器不会跟踪所有内容。例如,像用户创建的类这样的复杂对象总是被跟踪。但是不会跟踪仅包含简单对象(如整数和字符串)的字典,因为该特定字典中的任何对象都不会包含对其他对象的引用。不能保存对其他元素(如整数和字符串)的引用的简单对象永远不会被跟踪。

如何使用 gc 模块

通常,垃圾收集器不需要调整即可运行良好。Python 的开发团队选择了反映最常见现实世界场景的默认值。但是如果你确实需要调整垃圾收集的工作方式,你可以使用Python 的 gc 模块。该gc模块为垃圾收集器的行为提供编程接口,并提供对正在跟踪的对象的可见性。

gc当你确定不需要垃圾收集器时,你可以做的一件有用的事情是关闭它。例如,如果你有一个堆放大量对象的短运行脚本,则不需要垃圾收集器。脚本结束时,所有内容都将被清除。为此,你可以使用命令禁用垃圾收集器gc.disable()。稍后,你可以使用 重新启用它gc.enable()。

你还可以使用 手动运行收集周期gc.collect()。一个常见的应用是管理程序的性能密集型部分,该部分会生成许多临时对象。你可以在程序的该部分禁用垃圾收集,然后在最后手动运行收集并重新启用收集。

另一个有用的垃圾收集优化是gc.freeze(). 发出此命令时,垃圾收集器当前跟踪的所有内容都被“冻结”,或者被列为免于将来的收集扫描。这样,未来的扫描可以跳过这些对象。如果你有一个程序在启动之前导入库并设置大量内部状态,那么你可以gc.freeze()在所有工作完成后发出。这使垃圾收集器不必搜寻那些无论如何都不太可能被删除的东西。(如果你想对冻结的对象再次执行垃圾收集,请使用gc.unfreeze().)

使用 gc 调试垃圾收集

你还可以使用它gc来调试垃圾收集行为。如果你有过多的对象堆积在内存中并且没有被垃圾收集,你可以使用gc's 检查工具来找出可能持有对这些对象的引用的对象。

如果你想知道哪些对象持有对给定对象的引用,可以使用gc.get_referrers(obj)列出它们。你还可以使用gc.get_referents(obj)来查找给定对象引用的任何对象。

如果你不确定给定对象是否是垃圾收集的候选对象,gc.is_tracked(obj)请告诉你垃圾收集器是否跟踪该对象。如前所述,请记住垃圾收集器不会跟踪“原子”对象(例如整数)或仅包含原子对象的元素。

如果你想亲自查看正在收集哪些对象,可以使用 设置垃圾收集器的调试标志gc.set_debug(gc.DEBUG_LEAK|gc.DEBUG_STATS)。这会将有关垃圾收集的信息写入stderr。它将所有作为垃圾收集的对象保留在只读列表中。

避免 Python 内存管理中的陷阱

如前所述,如果你在某处仍有对它们的引用,则对象可能会堆积在内存中而不会被收集。这并不是 Python 垃圾收集本身的失败。垃圾收集器无法判断你是否不小心保留了对某物的引用。

让我们以一些防止对象永远不会被收集的指针作为结尾。

注意对象范围

如果你将对象 1 指定为对象 2 的属性(例如类),则对象 2 将需要超出范围,然后对象 1 才会:

obj1 = MyClass()
obj2.prop = obj1

更重要的是,如果这种情况发生在某种其他操作的副作用中,例如将对象 2 作为参数传递给对象 1 的构造函数,你可能不会意识到对象 1 持有一个引用:

obj1 = MyClass(obj2)

另一个例子:如果你将一个对象推入模块级列表并忘记该列表,则该对象将一直保留,直到从列表中删除,或者直到列表本身不再有任何引用。但是如果该列表是一个模块级对象,它可能会一直存在,直到程序终止。

简而言之,请注意你的对象可能被另一个看起来并不总是很明显的对象持有的方式。

使用 weakref避免引用循环

Python 的 weakref 模块允许你创建对其他对象的弱引用。弱引用不会增加对象的引用计数,因此只有弱引用的对象是垃圾回收的候选对象。

一个常见的用途weakref是对象缓存。你不希望仅仅因为它具有缓存条目而保留引用的对象,因此你将 aweakref用于缓存条目。

手动中断参考循环

最后,如果你知道给定对象包含对另一个对象的引用,你总是可以手动中断对该对象的引用。例如,如果你有instance_of_class.ref = other_object,你可以设置instance_of_class.ref = None何时准备删除 instance_of_class。

通过了解 Python 内存管理的工作原理,我们对其垃圾收集系统如何帮助优化 Python 程序中的内存,以及如何使用标准库和其他地方提供的模块来控制内存使用和垃圾收集。

 

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

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

相关文章

唯杰地图之前端CAD图GIS数据访问权限配置

前言 数字经济时代,数据要素的价值日益凸显,与之相应的,数据安全问题也越来越受到重视。唯杰地图 VJMAP为CAD图或自定义地图格式WebGIS可视化显示开发提供的一站式解决方案,支持的格式如常用的AutoCAD的DWG格式文件、GeoJSON等常用GIS文件格式,它使用WebGL矢量图块和栅格瓦…

一文清晰讲明白DDD(领域驱动设计)的知识点

什么是DDD DDD(领域驱动设计)是一种处理高度复杂领域的设计思想,是一种架构设计方法论,是一种设计模式。以高内聚低耦合为目的,把一个复杂的软件应用系统中各个部分进行一个很好的拆解和封装,对软件系统进…

运算放大器积分电路上并联的电阻什么作用

学过模电的同学对运放积分电路应该都不会陌生,基本电路如下图中所示 积分电路主要是用来进行波形变换,放大电路失调的消除,以及反馈控制中的积分补偿。 常用积分电路将方波变幻成三角波,或者正弦波变成余弦波,今天我们…

拍照识别花草软件有哪些?识别植物花草的软件哪个准?

不知道有没有小伙伴和我一样,好奇心比较旺盛,遇到问题都喜欢打破砂锅问到底。就连平时在路上遇到一些好看的花花草草时,我都想知道它是什么。但是花草这些就比较特殊,想了解它的身份,光靠描述可行不通。借助识别工具来…

UEC++ 代理/委托

代理: 代理可以帮助我们解决一对一或是一对多的任务分配工作。主要可以帮助我们解决通知问题。我们可以通过代理完成调用某一个对象的一个函数,而不直接持有该对象的任何指针。代理就是为你跑腿送信的,你可以不用关心给送信的目标人具体是谁…

异步线程使用Request存在问题

概述 如果我们将request传递到异步线程中使用,可能获取不到参数,并且会导致后续的请求,使用到这个线程也会出问题。 原因就是request对象会被重复使用。 源码分析 1、获取参数 先看一个非常重要的方法,getParameter 方法调用第…

计算机毕业设计之java+javaweb的美容院管理系统

计算机毕业设计之javajavaweb的美容院管理系统 项目介绍 系统权限按管理员、用户、医生和美容师这四类涉及用户。 (a) 管理员:进入系统可以实现主页、个人中心、用户管理、医生管理、美容师管理、项目部门管理、项目类型管理、产品分类管理、产品信息管理、医美项目…

JavaEE:进程调度的基本过程

目录 进程是什么? 操作系统对进程的调度 2.1 PCB中的信息 2.2 进程的调度是如何进行的呢? 并行: 并发: 总结: 进程是什么? 如果想了解进程调度的基本过程,我们首先要了解的是进程是什么? 咱们可以在任务管理器中看到 这一切跑起来的程序就是进程! 操作系统对进程的调…

22.this指针

1.this指针工作原理 我们知道,c++的数据和操作也是分开存储,并且每一个非内联成员函数(non-inline member function)只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码那么问题是:这一块代码是如何区分那个对象调用自己的呢?c++通过提供特殊的对象指针,this…

Python面向对象笔记

一、面向对象 (一)基本概念 (1)面向对象编程 —— Object Oriented Programming 简写 OOP (2)面向对象三大特性封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中定义类的准则封装 是面向对象编程的一大特点 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一…

王道-考研-数据结构-线索二叉树

线索二叉树的构造 常用的是中序线索二叉树。 寻找前驱结点:若左指针为线索,则其指向结点为前驱结点。 若左指针为左孩子,则其左子树的最右侧结点为前驱结点。寻找后继结点:若右指针为线索,则其指向结点为后继结点。 若右指针为右孩子,则其右子树的最左侧结点为后继结点。…

Vue支持多文件上传 前端+后端 (详细介绍)

前端vue后端java支持多文件上传效果图Vue部分后台部分效果图 可以上传多个文件 Vue部分 <template><div><el-form-item label"案例名称" prop"caseName"><el-input v-model"formObj.caseName" placeholder"请输入案…

计算机毕业设计之java+javaweb的网上电子书店-图书商城网站

计算机毕业设计之javajavaweb的网上电子书店-图书商城网站 项目介绍 系统权限按管理员和用户这两类涉及用户划分。 (a) 管理员&#xff1a;管理员使用本系统涉到的功能主要有主页、个人中心、用户管理、一级分类管理、二级分类管理、电子书管理、下单购买管理、我的书籍管理、留…

MUR1100-ASEMI快恢复二极管MUR1100

编辑-Z MUR1100在DO-41封装里采用的1个芯片&#xff0c;其尺寸都是50MIL&#xff0c;是一款快恢复二极管。MUR1100的浪涌电流Ifsm为35A&#xff0c;漏电流(Ir)为10uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。MUR1100采用GPP硅芯片材质&#xff0c;里面有1颗芯片组成。…

如何用Vue + Mint UI实现上拉加载更多?

引言: 上拉加载更多在移动端不论是在 app 里面还是在页面中都是必不可少的&#xff0c;以下是 mint-ui 中上拉加载更多的总结。 一、在项目中使用 mint-ui 需要先安装 查看官网 (1)安装:npm i mint-ui --save (2)在 vue 中 main.js 引入 import MintUi from mint-ui import mi…

图扑数字孪生军事营区,实现主动防御

前言 20 世纪 50 年代初中国人民解放军开始自建营区。传统营区管理系统以独立的“点状”系统为主&#xff0c;缺乏集控平台&#xff0c;全局管理复杂度高。70 年代末提出建设智能化营区&#xff0c;并向“数字化、智能化、网络化、互动化、融合化”的方向靠拢。通过建设集光电…

【车辆配送】基于模拟退火 (SA)求解车辆配送 (VPR) (Matlab代码实现)

目录 1 车辆配送问题 2 模拟退火法 3 Matlab代码实现 4 实现结果 5 参考文献 6 写在最后 1 车辆配送问题 式(9)~( 12)中, 为配送车辆到达需求点i的时间;为需求点i到需求点j的运输成本;、分别为配送车辆提前到达需求点i的或者滞后到达需求点i的单位时间内的等待成本以及惩…

C语言编译过程——预处理、编译汇编和链接详解

引言 C语言经典的 “hello world ” 程序&#xff0c;伴随着每个程序员一起步入编程世界的大门。从编写、编译到运行&#xff0c;看到屏幕上输出的“hello world ”&#xff0c;那么你知道它都经历了什么吗&#xff1f;今天我们就来聊聊这个话题。 一、从hello.c聊起 hello …

Java多线程~线程的状态以及状态转移的条件

目录 线程的六种状态 状态转移的条件 NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED 线程的六种状态 线程共有六种状态&#xff0c;分别为&#xff1a; NEW(初始状态)&#xff1a;new表示新建一个线程对象&#xff0c;即安排了工作&#xff0c;但未开始行…

Ubuntu指令说明

1、ls ls命令是list的缩写&#xff0c;用来打印出当前目录的清单。如果ls指定其他目录&#xff0c;那么就会显示指定目录里的文件及文件夹清单。通过ls命令不仅可以查看linux文件夹包含的文件&#xff0c;而且可以查看文件权限&#xff08;包括目录、文件夹、文件权限&#xf…