• Python的内存管理和垃圾回收机制


    前言

     为什么 已经 del 析构了 name 变量,然后新的变量 xxxxx的 内存地址却跟原来name一样呢?

    带着这个疑问看看了Python的内存管理机制。

     

    一、内存管理机制

    Python语言是由C实现的,所以想要剖析Python的内存管理机制,就需要下载Python的源码包看看C源代码是怎么写的?

    C源码有2个关键目录

    Include

    Objects

    1.两个重要的结构体

    #define _PyObject_HEAD_EXTRA            
        struct _object *_ob_next;           
        struct _object *_ob_prev;
         
    #define PyObject_HEAD       PyObject ob_base;
     
    #define PyObject_VAR_HEAD      PyVarObject ob_base;
     
     
    typedef struct _object {
        _PyObject_HEAD_EXTRA // 用于构造双向链表
        Py_ssize_t ob_refcnt;  // 引用计数器
        struct _typeobject *ob_type;    // 数据类型
    } PyObject;
     
     
    typedef struct {
        PyObject ob_base;   // PyObject对象
        Py_ssize_t ob_size; /* Number of items in variable part,即:元素个数 */
    } PyVarObject;
    include/object.h

    Python中所有类型创建对象时,底层都是PyObject和PyVarObject结构体实现,一般情况下由单个元素组成对象内部会使用PyObject结构体(float)、由多个元素组成的对象内部会使用PyVarObject结构体(str/int/list/dict/tuple/set/自定义类),因为由多个元素组成的话是需要为其维护一个 ob_size(内部元素个数)。

    Python 执行 v=0.4
    C语言 1.开辟内存 2.数据初始化
      ob_fval
    =0.3
    ob_type=float
    ob_refcnt=1
    3.将对象放到双向链表中 ref_chain

    Python 执行 name=v
    0.不会重新开辟内存
    1.
    ob_refcnt=1
    Python执行 del v
    ob_refcnt-1

    Python执行
      def fuc(arg):
        print(123)
      func(name)
    1.arg参数刚进去 ob_refcnt+1
    2.fuc执行完  ob_refcnt-1
    Python执行 del name
    1.ob_refcnt-1
    每次ob_refcnt-1都检查是否为0

    如果引用计数器为0就按理说 内存就应该销毁。
    但是Python为了提升效率, 会对Python 某些数据类型做一些缓存机制;
    为了减少开辟内存和销毁内存占用的时间,我们会把引用计数器为0的对象放到双向链表中,方便下次创建float类型时可以继续使用原来的内存地址
    
    

    内存管理概述

    Python是由C语言开发的解释器,任何操作Python都会调用C的代码。

    PyObject: 指向上1个值指针、指向下1个值的指针、计数器、类型

    PyobjectVarObject: PyObject、容器个数

    在Python中每创建个对象,都会由C语言结构体内部都要维护4个值:双向链表、ob_refcnt、ob_type之后对内存中的数据进行初始化。

    引用计数=0, 赋值然后将对象放到双向链表中refchain.

    以后再有其他变量指向该内存 引用计算器+=1,如果销毁某个变量,则找到指向的内存,讲其计数器引用-1

    引用计数器如果引用为0,则进行垃圾回收。

    但是某些数据在内部可能存在缓存机制  例如float/list/int,在其引用计数器引用计数器=0时,不会真正销毁,而是放在 1个叫 free_list的链表中。

    如果后期再创建同类型数据时,会取出链表中的对象,然后对对象进行初始化操作,重新赋上新值。

     二、垃圾回收机制

    Python的垃圾回收机制,以引用计数为主,标记清除、分代回收为辅。 

    1.引用计数

    如果在Python里面创建了1个a=10的变量,那么a变量就指向了对象10。

    Cpython解释器会记录对象10这个对象被变量使用的数量,当这个对象引用数量为0时 被回收。

    2.标记清除

    标记清除策略是对引用计数测试的补充,解决的问题就是当容器数据类型(list、tuple、dict、object)类数据  引用计数为0时,但是它们内部的数据却引用着其他数据。

    我现在变量a、b已经访问不到 对象A、B了,但是A、B对象依然引用着其他数据。所以我需要把对象A、B都回收掉。 

    class A():
        pass
    
    class B():
        pass
    
    a=A()
    b=B()
    a.haha=b
    b.hehe=a
    #a---A
    #b---B
    a=None
    b=None
    
    print(a)

    解决之道

     

    标记清除(Mark—Sweep)算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?

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

    3.分代回收

    如果把Python中声明的所有容器类对象,都放在1个双向链表里进行循环检查 计数器是否为0 ?是否存在循环引用,是不是很耗时呢?

    我们可以先把新创建的变量放在1个地方,如果没有以上3种垃圾回收机制都没有回收掉它----------》就放到另1个地方-------》如果还没有被回收掉就放在------------》 下一个地方。这就是分代管理。

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

    解决之道

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

    参考

  • 相关阅读:
    Linux培训教程lgzip命令详解和使用实例
    Linux 新手应该知道的一些求助命令
    “变态教育创导者”兄弟连教育新三板挂牌上市
    linux中more命令如何使用
    html上标与下标应用
    linux命令大全之cal命令详解(显示日历)
    成为java高手的八大条件
    mysql 1055
    MySQL更改口令报错ERROR 1064
    centos7.5 安装mysql8.0
  • 原文地址:https://www.cnblogs.com/sss4/p/12334308.html
Copyright © 2020-2023  润新知