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 内存管理与垃圾回收的更多相关文章

  1. Python内存管理:垃圾回收

    http://blog.csdn.net/pipisorry/article/details/39647931 Python GC主要使用引用计数(reference counting)来跟踪和回收垃 ...

  2. python内存管理及垃圾回收

    一.python的内存管理 python内部将所有类型分成两种,一种由单个元素组成,一种由多个元素组成.利用不同结构体进行区分 /* Nothing is actually declared to b ...

  3. Java内存管理和垃圾回收

    笔记,深入理解java虚拟机 Java运行时内存区域 程序计数器,线程独占,当前线程所执行的字节码的行号指示器,每个线程需要记录下执行到哪儿了,下次调度的时候可以继续执行,这个区是唯一不会发生oom的 ...

  4. 面试题之C# 内存管理与垃圾回收

    面试题之C# 内存管理与垃圾回收 你说说C# 的内存管理是怎么样的 这句话我记了一个多礼拜了, 自从上次东北师大面试之后, 具体请看<随便扯扯东北师大的面试>. 国庆闲着没事, 就大概了解 ...

  5. Java内存管理及垃圾回收总结

    概述 Java和C++的一个很重要的差别在于对内存的管理.Java的自己主动内存管理及垃圾回收技术使得Java程序猿不须要释放废弃对象的内存.从而简化了编程的过程.同一时候也避免了因程序猿的疏漏而导致 ...

  6. C#内存管理与垃圾回收

    垃圾回收还得从根说起,就像生儿育女一样. 根:根是一个位置,存放一个指针,该指针指向托管堆中的一个对象,或是一个空指针不指向任何对象,即为null.根存在线程栈或托管堆中,大部分的跟都在线程栈上,因为 ...

  7. 使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)

    使用虚幻引擎中的C++导论(四)(终) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如 ...

  8. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  9. javascript中的内存管理和垃圾回收

    前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...

随机推荐

  1. 获取nginx日志状态码百分比脚本

    #!/bin/bash pwd=/app/nginx/logs/access.log for num1 in `cat $pwd | awk '{print $9}' | grep -Ei " ...

  2. java秒杀系列(1)- 秒杀方案总体思路

    前言 首先,要明确一点,高并发场景下系统的瓶颈出现在哪里,其实主要就是数据库,那么就要想办法为数据库做层层防护,减轻数据库的压力. 一.简单图示 我用一个比较简单直观的图来表达大概的处理思路 二.生产 ...

  3. Visualizing and Understanding Convolutional Networks论文复现笔记

    目录 Visualizing and Understanding Convolutional Networks 论文复现笔记 Abstract Introduction Approach Visual ...

  4. pod 详解

    静态pod是由kubelet进行管理的仅存在于特定的node上的pod. pod容器共享volume同一个pod中的多个容器能够共享pod级别的存储卷volume pod的配置管理  应用配置管理方案 ...

  5. 在 Dapr 中使用 Cron 绑定的计划任务

    我昨天写了一篇关于在微服务应用程序中采用Dapr的好处的文章<从服务之间的调用来看 我们为什么需要Dapr>[1], 在那篇文章中,我们专注于"服务调用"构建块 [2] ...

  6. Visual Studio 中快速创建方法 Generate a method in Visual Studio

    2020-04-04 https://docs.microsoft.com/en-us/visualstudio/ide/reference/generate-method?view=vs-2019 ...

  7. Git标签 简单操作

    感谢廖雪峰老师,以下内容多数来自老师的Git教程. 另有部分参考Git中文文档. 创建 命令git tag <tagname> [commit id]用于新建一个标签,默认为HEAD; 也 ...

  8. Android Studio 插件(不定期更新)

    GsonFormat 根据JSONObject格式的字符串,自动生成实体类参数. 安装 1.Android studio File->Settings-->Plugins -->in ...

  9. 使用kubeadm快速部署一套K8S集群

    一.Kubernetes概述 1.1 Kubernetes是什么 Kubernetes是Google在2014年开源的一个容器集群管理系统,Kubernetes简称K8S. K8S用于容器化应用程序的 ...

  10. js 用 void 0 替代 undefined

    underscore 源码没有出现 undefined,而用 void 0 代替之.为什么要这么做?我们可以从两部分解读,其一是 undefined 哪里不好了,你非得找个替代品?其二就是替代品为毛要 ...