python内存管理和垃圾回收一文详解(基于c语言源码底层逻辑)

引用计数器

首先我们大概回忆一下C语言中的环状双向链表,如图,在双向链表中对于一个结点来说会有前驱和后继:

C语言中基本的定义方式如下:

typedef struct {ElemType data;	 // 数据域Lnode* prior;  // 前驱指针域Lnode* next;	 // 后继指针域
}Lnode;

现在我们把这段基础代码拿到python中来详细看看会被怎么样拓展编写:

typedef struct _object {struct _object* ob_next;	// 下一个元素struct _object* ob_prev;	// 上一个元素Py_ssize ob_refcnt;			// 引用计数器struct _typeobject* ob_type;	// 数据类型 _typeobjec会根据类型替换
} PyObject;typedef struct {PyObject ob_base;	// PyObject对象Py_ssize_t ob_size;	// 元素个数
} PyVarObject;

在python中,这个环状链表C源码的表示如上,可以发现在它分为PyObject和PyVarObject两个结构体,在python底层C源码中每个类型都有其对应的结构体

PyObject是结点的固定变量(指向上一个的指针、指向下一个的指针、引用计数器、数据类型)构成的结构体

PyVarObject是有多个元素组成的对象(例如一个列表L=['a','b','c'])构成的结构体

我们把目光看到引用计数器,首先我们要知道,上面的这样一个环形双向链表在python中称为refchain双线链表,当python程序执行的时候,会根据不同的类型找到对应的类型,再根据结构体中的字段来创建相关的数据,然后将对象添加到refchain双线链表中

那我们来具体看看是怎么做的,例如在python中,你输入一行代码:a = 1.25,python会帮你创建出这些内容:

输入:a = 1.25创建:
_ob_prev = refchain的上一个对象
_ob_next = refchain的下一个对象
ob_refcnt = 1
ob_type = int
ob_fval = 1.25
  • _ob_prev 用于保存上一个对象
  • _ob_next 用于保存下一个对象
  • ob_refcnt = 1 引用计数器
  • ob_type = int 数据类型
  • ob_fval = 1.25 数据的值

可以看到由于定义了a=1.25,所以计数器记录了一次,当有其他值引用对象时候,计数器就会发生变化

a = 1.25 # ob_refcnt=1
b = a # ob_refcnt=2del b # ob_refcnt=1
del a # ob_refcnt=0

当我将计数器的值减成0的时候,程序中已经没有人再需要这个对象了,所以,这个对象也就变成了垃圾,那就要进行垃圾回收,程序就会将该对象完全从refchain中销毁,内存也会释放出来

现在你应该开始了解垃圾回收了吧,我们来回忆一下引用计数器和垃圾回收

因为程序会将我们创建的对象放到refchain环形双向链表中,而该链表的每个对象的结构中都有一个引用计数器ob_refcnt,当你创建的时候默认值就是1了,如果这个对象已经被你删除了,那就没有用即成为垃圾,程序就会进行垃圾回收,将该对象直接从链表中删除,此时该对象被销毁,他占有的内存空间也就被释放了

但是这样的引用计数器并不是万能的,他会面临着循环引用和交叉感染的风险,下面我们看这段代码:

v1 = [1, 2, 3]  # v1的ob_refcnt = 1
v2 = [4, 5, 6]  # v2的ob_refcnt = 1v1.append(v2)  # v2的ob_refcnt = 2
v2.append(v1)  # v1的ob_refcnt = 2del v1  # v1的ob_refcnt = 1
del v2  # v2的ob_refcnt = 1

虽然我们已经删除v1,v2,已经不需要使用他们,但是这两个变量始终无法彻底从refchain链表中完全销毁,就会占用多余空间,浪费资源

所以为了解决这个问题,python又引入了标记清除这个方法

标记清除

目的:解决引用计数器的循环引用的不足

实现:在python的底层又去维护了一个新链表,该链表里面专门存储这些有可能存在循环引用的对象

哪些对象可能存在循环引用呢?

如果只是单纯的引用赋值等,基本不会产生循环引用问题,你可以删除该但是当该对象是被另一个对象嵌套使用的时候,你是无法删除这个嵌套引用的操作的,此时就有可能产生循环引用问题,而能包含其他对象的有以下几种:列表、元组、集合、字典,所以,我们可以将这些怀疑对象放入新链表

python会定期去扫描这个新链表,就可以检查当中的元素是否有循环引用,如果有计数器-1后值=0,那就是垃圾,直接将其从refchain链表删除

但是这也有一个新问题:

  1. 什么时候扫描合适呢?如果一直扫描程序性能会受影响
  2. 其次是扫描列表这些,每一个子元素都要扫描,耗时就会比较久

为了解决这些问题,又有了分代回收

分代回收

为了解决标记清除的两个问题,分代回收方法将存储可能存在循环引用问题的对象分为三个链表,分别是0、1、2代链表

  • 0代:所有可能对象都先存储在0代链表中,当0代链表打到700个数据的时候,程序对该链表做一次扫描,将垃圾对象在refchain中销毁,剩余非垃圾对象全部移到1代链表中,此时1代链表会记录下0代链表扫描了1次,以此类推
  • 1代:当0代链表扫描10次后,1代链表扫描1次
  • 2代:当1代链表扫描10此后,2代链表扫描1次

这样扫描次数不会过多,同样的数据也无需重复扫描检查过多次

缓存机制

基于以上几步,python对其又做出了优化机制,即为python缓存,缓存基本分为两类,池和free_list

池(int...)

先说第一种缓存机制:池

v1 = 9  # 创建一个对象,变量名为v1,值为9
v2 = 8  # 创建一个对象,变量名为v2,值为8
v3 = 9  # 创建一个对象,变量名为v3,值为9

按理来说,三个变量应该是不同的地址,我们打印出来看看:

print(id(v1), id(v2), id(v3))
# 140737462441000 140737462440968 140737462441000

可以发现v1和v3的值相同于是两者的地址也相同

之所以这样是因为当我们创建一些简单变量的时候,为了避免反复创建销毁,python提前为我们准备好了一些数据存储起来,构成了一个数据池,当我们创建的对象值在这个范围内,程序会去池子里面找到该值,直接拿来用

例如int类型的值有-5~257,9早就已经被创建好放到池子里,v1和v3只是找了同一个地址上的值拿来用,所以两个地址也相同

str类型也会预存ascli字符

str1 = 'A'
str2 = 'A'print(id(str1),id(str2))
# 140737462484992 140737462484992

除此之外,python还给字符串设置了驻留机制,python中有一段常见代码:

str3 = 'python'
str4 = 'python'
print(id(str3) == id(str4))  # True

 此时‘python’可不是ascil字符表的内容了,之所以两者地址相同正因为这个驻留机制,使得对于这些只有下划线、数字、字母的简单字符串如果在内存中已经存在,那么再次创建就可以直接用原来地址,而不需要在free_list中存留

free_list(turple/list/set/dict)

l = [1, 2]
l1 = [1, 2]
l2 = [3, 4]print(id(l))  # 4336
print(id(l1))  # 9264
print(id(l2))  # 8752

当我们创建了三个列表,列表之间并不会因为数据相同就有相同地址,说明python并没有像池一样给我们缓存一些列表数据 

del l1
del l2l3 = [5, 6]
print(id(l3))  # 8752l4 = [7, 8, 9]
print(id(l4))  # 9264

接着我们删除了l1和l2,添加了新列表l3和l4,可以看到3和2的地址相同,4和1的地址相同,说明3用的是2的空间,4用的是1的空间,也就说明1和2虽然被删除了,但是还没有完全销毁,他们的位置被保存下来了

这个保存他们位置的地方就是free_list,像列表、元组、字典、集合这些数据类型,被删除的时候并不会直接被完全销毁,而是将其保存在free_list中,等到有相同类型被创建的时候,就会占用他们的位置,将内部的值和名字都改为新的

注意:元组在free_list内元组会以不同个数分别保存起来,当释放对应个数的元组时,会在free_list中找相应个数元组的地址

在free_list中:T=([1个元素的元组],[2个元素的元组],[3个元素的元组]....)t1 = (1, 2)
t2 = (3, 4, 5)del t1  # 将t1存放到有2个元素的元组中
del t2  # 将t2存放到有3个元素的元组中t3 = (1)  # 去只有1个元素的元组中找位置
t4 = (8, 9)  # 去只有2个元素的元组中找位置

此外,该free_list并不是无限的,当它满了一定数量,就不会再缓存了,而是照常直接销毁

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

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

相关文章

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Hyperlink)

超链接组件,组件宽高范围内点击实现跳转。 说明: 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。该组件仅支持与系统浏览器配合使用。 需要权限 使用网络时,需要申请权限ohos.per…

【PyTorch][chapter 22][李宏毅深度学习][ WGAN]【实战三】

前言: 本篇主要讲两个WGAN的两个例子: 1 高斯混合模型 WGAN实现 2 MNIST 手写数字识别 -WGAN 实现 WGAN 训练起来蛮麻烦的,如果要获得好的效果很多超参数需要手动设置 1: 噪声的维度 2: 学习率 3: 生成器,鉴别器…

【深度学习与神经网络】MNIST手写数字识别1

简单的全连接层 导入相应库 import torch import numpy as np from torch import nn,optim from torch.autograd import Variable import matplotlib.pyplot as plt from torchvision import datasets, transforms from torch.utils.data import DataLoader读入数据并转为ten…

[Python人工智能] 四十三.命名实体识别 (4)利用bert4keras构建Bert+BiLSTM-CRF实体识别模型

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解如何实现中文命名实体识别研究,构建BiGRU-CRF模型实现。这篇文章将继续以中文语料为主,介绍融合Bert的实体识别研究,使用bert4keras和kears包来构建Bert+BiLSTM-CRF模型。然而,该代码最终结…

vue2 实战:模板模式与渲染模式代码互切

显示效果 模板模式 <template><tr ><td class"my-td" v-if"element.isInsert1"><el-button type"danger" circle size"mini" class"delete-btn" title"删除" click"deleteItem()&quo…

KMM初探

什么是KMM&#xff1f; 在开始使用 KMM 之前&#xff0c;您需要了解 Kotlin。 KMM 全称&#xff1a;Kotlin Multiplatform Mobile&#xff09;是一个用于跨平台移动开发的 SDK,相比于其他跨平台框架&#xff0c;KMM是原生UI逻辑共享的理念,由KMM封装成Android(Kotlin/JVM)的aar…

AI大模型智能大气科学探索之:ChatGPT在大气科学领域建模、数据分析、可视化与资源评估中的高效应用及论文写作

本文深度探讨人工智能在大气科学中的应用&#xff0c;特别是如何结合最新AI模型与Python技术处理和分析气候数据。课程介绍包括GPT-4等先进AI工具&#xff0c;旨在帮助大家掌握这些工具的功能及应用范围。本文内容覆盖使用GPT处理数据、生成论文摘要、文献综述、技术方法分析等…

Java 学习和实践笔记(41):API 文档以及String类的常用方法

JDK 8用到的全部类的文档在这里下载&#xff1a; Java Development Kit 8 文档 | Oracle 中国

docker入门(一)—— docker概述

docker 概述 docker 官网&#xff1a;http://www.docker.com 官网文档&#xff1a; https://docs.docker.com/get-docker/ Docker Hub官网&#xff1a;https://hub.docker.com &#xff08;仓库&#xff09; 什么是 docker docker 是一个开源的容器化平台&#xff0c;可以…

FSP40罗德与施瓦茨FSP40频谱分析仪

181/2461/8938产品概述&#xff1a; 频率范围:9千赫至40千兆赫 分辨率带宽:1赫兹至10兆赫 显示的平均噪音水平:-155分贝&#xff08;1赫兹&#xff09; 相位噪声:10 kHz时为-113 dB&#xff08;1Hz&#xff09; 附加滤波器:100 Hz至5 MHz的通道滤波器和RRC滤波器、1 Hz至3…

数据仓库系列总结

一、数据仓库架构 1、数据仓库的概念 数据仓库&#xff08;Data Warehouse&#xff09;是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持管理决策。 数据仓库通常包含多个来源的数据&#xff0c;这些数据按照主题进行组织和存储&#x…

Springboot+vue的仓库管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的仓库管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

Leetcode 62. 不同路径

心路历程&#xff1a; 这道题基本就是Q-learning经典迷宫问题的简化版本&#xff0c;所以肯定是用动态规划了&#xff0c;毕竟RL中的时序差分估计法的本身也是来自于MC和动态规划的结合。如果正常正向思维思考的话&#xff0c;首先看不到问题明显的循环结构&#xff0c;考虑递…

秒级生图,大模型 SDXL-turbo、LCM-SDXL 实战案例来了

最近一个月&#xff0c;快速生图成为文生图领域的热点&#xff0c;其中比较典型的两种方式的代表模型分别为SDXL-turbo 和 LCM-SDXL。 SDXL-turbo 模型是 SDXL 1.0 的蒸馏版本&#xff0c;SDXL-Turbo 基于一种称之为对抗扩散蒸馏&#xff08;ADD&#xff09;的新颖的训练方法&…

Go 1.22 - 更加强大的 Go 执行跟踪

原文&#xff1a;Michael Knyszek - 2024.03.14 runtime/trace 包含了一款强大的工具&#xff0c;用于理解和排查 Go 程序。这个功能可以生成一段时间内每个 goroutine 的执行追踪。然后&#xff0c;你可以使用 go tool trace 命令&#xff08;或者优秀的开源工具 gotraceui&a…

Spring Cloud 整合 GateWay

目录 第一章 微服务架构图 第二章 Spring Cloud整合Nacos集群 第三章 Spring Cloud GateWay 第四章 Spring Cloud Alibaba 整合Sentinel 第五章 Spring Cloud Alibaba 整合SkyWalking链路跟踪 第六章 Spring Cloud Alibaba 整合Seata分布式事务 第七章 Spring Cloud 集成Auth用…

[Qt学习笔记]Release后的exe程序在新的电脑上出现“找不到MSVCP140.dll”的错误

1、背景介绍 我们在打包程序的时候一般都会把相关依赖库整体打包&#xff0c;这样程序在新的电脑和环境下就不需要再去配置对应的环境&#xff0c;但是有时候新程序在一台新的电脑运行时会出现“找不到MSVCP140.dll”这种错误&#xff0c;其原因就是在新电脑的操作系统中缺少一…

倒计时 7 天 | 立即加入 GDE 成长计划,飞跃成为谷歌开发者专家

谷歌开发者专家 (Google Developer Experts&#xff0c;GDE)&#xff0c;又称谷歌开发者专家项目&#xff0c;是由一群经验丰富的技术专家、具有社交影响力的开发者和思想领袖组成的全球性社区。通过在各项活动演讲以及各个平台上发布优质内容来积极助力开发者、企业和技术社区…

AI助手 - 月之暗面 Kimi.ai

前言 这是 AI工具专栏 下的第四篇&#xff0c;这一篇所介绍的AI&#xff0c;也许是截至今天&#xff08;204-03-19&#xff09;国内可访问的实用性最强的一款。 今年年初&#xff0c;一直看到有人推荐 Kimi&#xff0c;不过面对雨后春笋般的各类品质的AI&#xff0c;说实话也有…

windows 多网卡情况dns解析超时问题的排查

最近遇到一个问题 多网卡&#xff0c;多网络环境下&#xff0c;dns解析总是超时。 排查之后发现是dns配置的问题&#xff0c;一个有线网络配置的内网dns&#xff0c;一个无线网络配置的公网dns 访问公网时莫名的时不时出现超时现象 初步排查是dns解析的耗时太长&#xff0c;…