Python 内存管理与垃圾回收
Python 内存管理与垃圾回收
参考文献:https://pythonav.com/wiki/detail/6/88/
引用计数器为主标记清除和分代回收为辅 + 缓存机制
1.1 大管家refchain
在Python的C源码中有一个名为refchain的环状双向链表
,这个链表比较牛逼了,因为Python程序中一旦创建对象都会把这个对象添加到refchain这个链表中。也就是说他保存着所有的对象。例如:
age = 18
name = "武沛齐"
1.2 引用计数器
在refchain中的所有对象内部都有一个ob_refcnt
用来保存当前对象的引用计数器,顾名思义就是自己被引用的次数,例如:
age = 18
name = "武沛齐" # 创建默认值是1
nickname = name # 创建后引用,引用次数+1
上述代码表示内存中有 18 和 “武沛齐” 两个值,他们的引用计数器分别为:1、2 。
当值被多次引用时候,不会在内存中重复创建数据,而是引用计数器+1
。 当对象被销毁时候同时会让引用计数器-1
,如果引用计数器为0,则将对象从refchain链表中摘除,同时在内存中进行销毁(暂不考虑缓存等特殊情况)。
age = 18
number = age # 对象18的引用计数器 + 1
del age # 对象18的引用计数器 - 1
def run(arg):
print(arg)run(number)# 刚开始执行函数时,对象18引用计数器 + 1,当函数执行完毕之 后,对象18引用计数器 - 1 。
num_list = [11,22,number] # 对象18的引用计数器 + 1
1.3 标记清除&分代回收
基于引用计数器进行垃圾回收非常方便和简单,但他还是存在循环引用
的问题,导致无法正常的回收一些数据,例如:
v1 = [11,22,33] # refchain中创建一个列表对象,由于v1=对象,所以列表引对象用计数器为1.
v2 = [44,55,66] # refchain中再创建一个列表对象,因v2=对象,所以列表对象引用计数器为1.
v1.append(v2) # 把v2追加到v1中,则v2对应的[44,55,66]对象的引用计数器加1,最终为2.
v2.append(v1) # 把v1追加到v1中,则v1对应的[11,22,33]对象的引用计数器加1,最终为2.
del v1 # 引用计数器-1
del v2 # 引用计数器-1
对于上述代码会发现,执行del
操作之后,没有变量再会去使用那两个列表对象,但由于循环引用的问题,他们的引用计数器不为0,所以他们的状态:永远不会被使用、也不会被销毁。项目中如果这种代码太多,就会导致内存一直被消耗,直到内存被耗尽,程序崩溃。
为了解决循环引用的问题,引入了标记清除
技术,专门针对那些可能存在循环引用的对象进行特殊处理,可能存在循环应用的类型有:列表、元组、字典、集合、自定义类等那些能进行数据嵌套的类型。
标记清除:创建特殊链表专门用于保存 列表、元组、字典、集合、自定义类等对象,之后再去检查这个链表中的对象是否存在循环引用,如果存在则让双方的引用计数器均 - 1 。
分代回收:对标记清除中的链表进行优化,将那些可能存在循引用的对象拆分到3个链表,链表称为:0/1/2三代,每代都可以存储对象和阈值,当达到阈值时,就会对相应的链表中的每个对象做一次扫描,除循环引用各自减1并且销毁引用计数器为0的对象。
// 分代的C源码
#define NUM_GENERATIONS 3
struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */ {{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0}, // 0代 {{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)}, 10, 0}, // 1代 {{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)}, 10, 0}, // 2代};
特别注意:0代和1、2代的threshold和count表示的意义不同。
- 0代,count表示0代链表中对象的数量,threshold表示0代链表对象个数阈值,超过则执行一次0代扫描检查。
- 1代,count表示0代链表扫描的次数,threshold表示0代链表扫描的次数阈值,超过则执行一次1代扫描检查。
- 2代,count表示1代链表扫描的次数,threshold表示1代链表扫描的次数阈值,超过则执行一2代扫描检查。
1.4 情景模拟
根据C语言底层并结合图来讲解内存管理和垃圾回收的详细过程。
第一步:当创建对象age=19
时,会将对象添加到refchain链表中。
第二步:当创建对象num_list = [11,22]
时,会将列表对象添加到 refchain 和 generations 0代中。
第三步:新创建对象使generations的0代链表上的对象数量大于阈值700时,要对链表上的对象进行扫描检查。
当0代大于阈值后,底层不是直接扫描0代,而是先判断2、1是否也超过了阈值。
- 如果2、1代未达到阈值,则扫描0代,并让1代的 count + 1 。
- 如果2代已达到阈值,则将2、1、0三个链表拼接起来进行全扫描,并将2、1、0代的count重置为0.
- 如果1代已达到阈值,则讲1、0两个链表拼接起来进行扫描,并将所有1、0代的count重置为0.
对拼接起来的链表在进行扫描时,主要就是剔除循环引用和销毁垃圾,详细过程为:
扫描链表,把每个对象的引用计数器拷贝一份并保存到
gc_refs
中,保护原引用计数器。再次扫描链表中的每个对象,并检查是否存在循环引用,如果存在则让各自的
gc_refs
减 1 。再次扫描链表,将
gc_refs
为 0 的对象移动到unreachable
链表中;不为0的对象直接升级到下一代链表中。处理
unreachable
链表中的对象的 析构函数 和 弱引用,不能被销毁的对象升级到下一代链表,能销毁的保留在此链表。
- 析构函数,指的就是那些定义了
__del__
方法的对象,需要执行之后再进行销毁处理。 - 弱引用,
- 析构函数,指的就是那些定义了
最后将
unreachable
中的每个对象销毁并在refchain链表中移除(不考虑缓存机制)。
至此,垃圾回收的过程结束。
缓存机制,请见参考文献。
Python 内存管理与垃圾回收的更多相关文章
- Python内存管理:垃圾回收
http://blog.csdn.net/pipisorry/article/details/39647931 Python GC主要使用引用计数(reference counting)来跟踪和回收垃 ...
- python内存管理及垃圾回收
一.python的内存管理 python内部将所有类型分成两种,一种由单个元素组成,一种由多个元素组成.利用不同结构体进行区分 /* Nothing is actually declared to b ...
- Java内存管理和垃圾回收
笔记,深入理解java虚拟机 Java运行时内存区域 程序计数器,线程独占,当前线程所执行的字节码的行号指示器,每个线程需要记录下执行到哪儿了,下次调度的时候可以继续执行,这个区是唯一不会发生oom的 ...
- 面试题之C# 内存管理与垃圾回收
面试题之C# 内存管理与垃圾回收 你说说C# 的内存管理是怎么样的 这句话我记了一个多礼拜了, 自从上次东北师大面试之后, 具体请看<随便扯扯东北师大的面试>. 国庆闲着没事, 就大概了解 ...
- Java内存管理及垃圾回收总结
概述 Java和C++的一个很重要的差别在于对内存的管理.Java的自己主动内存管理及垃圾回收技术使得Java程序猿不须要释放废弃对象的内存.从而简化了编程的过程.同一时候也避免了因程序猿的疏漏而导致 ...
- C#内存管理与垃圾回收
垃圾回收还得从根说起,就像生儿育女一样. 根:根是一个位置,存放一个指针,该指针指向托管堆中的一个对象,或是一个空指针不指向任何对象,即为null.根存在线程栈或托管堆中,大部分的跟都在线程栈上,因为 ...
- 使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)
使用虚幻引擎中的C++导论(四)(终) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如 ...
- Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收
很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...
- javascript中的内存管理和垃圾回收
前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...
随机推荐
- 搭建sublime.txt环境结合使用python
{ "cmd": ["python3", "-u", "$file"] }
- python网络爬虫-解析网页(六)
解析网页 主要使用到3种方法提取网页中的数据,分别是正则表达式.beautifulsoup和lxml. 使用正则表达式解析网页 正则表达式是对字符串操作的逻辑公式 .代替任意字符 . *匹配前0个或多 ...
- Android开发-记账本-实现记账功能选择
制作GridView适配器,实现页面数据的变化 制作类型存储数据库,存储的主要是图片类型,类型被选中时的图片,类型未被选中时的图片. 数据库代码如下 package com.example.Utils ...
- python 元组tuple介绍,使用。
原文 https://blog.csdn.net/ruanxingzi123/article/details/83184909 一 是什么? # python 元组tuple? ''' 元祖tupl ...
- 「 MySQL高级篇 」MySQL索引原理,设计原则
大家好,我是melo,一名大二后台练习生,大年初三,我又来充当反内卷第一人了!!! 专栏引言 MySQL,一个熟悉又陌生的名词,早在学习Javaweb的时候,我们就用到了MySQL数据库,在那个阶段, ...
- 「SDWC2018 Day1」网格
题目当中有三条限制,我们来逐一考虑.对于第一条限制,每次走动的增加量 \(x_i \le M_x, y_i \le M_y\),可以发现一共走的步数是确定的,那么就相当于解这样两个方程组: \[x_1 ...
- 1.k8s的前世今生
k8s是Kubernetes的缩写,Google 于 2014 年开源了 Kubernetes 项目. 一.k8s的历史演变 k8s的演变过程:首先从传统的服务-->虚拟机部署-->容器部 ...
- JFrame 的层次结构 及 背景设置说明
感谢原文:https://blog.csdn.net/qq_32006373/article/details/49659129 一.JFrame 的层次结构 我们通过两个图来说明一下 JFrame 的 ...
- JVM学习十三 - (复习)HotSpot 虚拟机对象探秘
对象的内存布局 在 HotSpot 虚拟机中,对象的内存布局分为以下 3 块区域: 对象头(Header) 实例数据(Instance Data) 对齐填充(Padding) 对象头 对象头记录了对象 ...
- Linux shell脚本之 if条件判断 (转)
IF条件判断 1.基本语法: if [ command ]; then 符合该条件执行的语句 fi 2.扩展语法: if [ command ];then 符合该条件执行的语句 elif [ comm ...