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


    结构

    Python解释器由c语言开发完成,py中所有的操作最终都由底层的c语言来实现并完成,所以想要了解底层内存管理需要结合python源码来进行解释。

    两个重要的结构体

    include/object.h

     1 #define _PyObject_HEAD_EXTRA            
     2     struct _object *_ob_next;           
     3     struct _object *_ob_prev;
     4      
     5 #define PyObject_HEAD       PyObject ob_base;
     6  
     7 #define PyObject_VAR_HEAD      PyVarObject ob_base;
     8  
     9  
    10 typedef struct _object {
    11     _PyObject_HEAD_EXTRA // 用于构造双向链表
    12     Py_ssize_t ob_refcnt;  // 引用计数器
    13     struct _typeobject *ob_type;    // 数据类型
    14 } PyObject;
    15  
    16  
    17 typedef struct {
    18     PyObject ob_base;   // PyObject对象
    19     Py_ssize_t ob_size; /* Number of items in variable part,即:元素个数 */
    20 } PyVarObject;

    以上源码是Python内存管理中的基石,其中包含了:

    • 2个结构体
      • PyObject,此结构体中包含3个元素。
        • _PyObject_HEAD_EXTRA,用于构造双向链表。
        • ob_refcnt,引用计数器。
        • *ob_type,数据类型。
      • PyVarObject,次结构体中包含4个元素(ob_base中包含3个元素)
        • ob_base,PyObject结构体对象,即:包含PyObject结构体中的三个元素。
        • ob_size,内部元素个数。
    • 3个宏定义
      • PyObject_HEAD,代指PyObject结构体。
      • PyVarObject_HEAD,代指PyVarObject对象。
      • _PyObject_HEAD_EXTRA,代指前后指针,用于构造双向队列。

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

    内存管理

    float

    # 第一步:根据float类型所需的内存大小,为其开辟内存。
    # 第二步:对新开辟的内存中进行类型和引用的初始化
        float类型每次创建对象时都会把对象放一个双向链表中.
        引用时,会对其引用计数器+1的动作.
    # 第三部: 销毁对象
        但float内部有缓存机制,所以他的执行流程是这样的
            float内部缓存的内存个数已经大于等于100,那么在执行`del val`的语句时,内存中就会直接删除此对象。
            未达到100时,那么执行 `del val`语句,不会真的在内存中销毁对象,而是将对象放到一个free_list的单链表中,以便以后的对象使用。
        从双向链表中移除

    垃圾回收机制

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

    1.引用计数器

    每个对象内部都维护了一个值,该值记录这此对象被引用的次数,如果次数为0,则Python垃圾回收机制会自动清除此对象。下图是Python源码中引用计数器存储的代码。

    2.循环引用

    通过引用计数器的方式基本上可以完成Python的垃圾回收,但它还是具有明显的缺陷,即:“循环引用”

    循环引用

    # 在内存创建两个对象,即:引用计数器值都是1
    # 两个对象循环引用,导致内存中对象的应用+1,即:引用计数器值都是2
    # 删除变量,并将引用计数器-1。
    # 关闭垃圾回收机制,因为python的垃圾回收机制是:引用计数器、标记清除、分代回收 配合已解决循环引用的问题,关闭他便于之后查询内存中未被释放对象。
    # 至此,由于循环引用导致内存中创建的obj1和obj2两个对象引用计数器不为0,无法被垃圾回收机制回收。
    # 所以,内存中Foo类的对象就还显示有2个。

    循环引用的问题会引发内存中的对象一直无法释放,从而内存逐渐增大,最终导致内存泄露

    为了解决循环引用的问题,Python又在引用计数器的基础上引入了标记清除和分代回收的机制。

    标记清除&分代回收

    Python为了解决循环引用,针对 lists, tuples, instances, classes, dictionaries, and functions 类型,每创建一个对象都会将对象放到一个双向链表中,每个对象中都有 _ob_next 和 _ob_prev 指针,用于挂靠到链表中。

    随着对象的创建,该双向链表上的对象会越来越多。

    • 当对象个数超过 700个 时,Python解释器就会进行垃圾回收。

    • 当代码中主动执行 gc.collect() 命令时,Python解释器就会进行垃圾回收。

    Python解释器在垃圾回收时,会遍历链表中的每个对象,如果存在循环引用,就将存在循环引用的对象的引用计数器 -1,同时Python解释器也会将计数器等于0(可回收)和不等于0(不可回收)的一分为二,把计数器等于0的所有对象进行回收,把计数器不为0的对象放到另外一个双向链表表(即:分代回收的下一代)。

     

    总结

    1. 开启一个新的对象会存放到双端链表中
    2. 通过引用计数来决定是不是垃圾,但是会有循环引用的问题
    3. 为了解决循环引用,使用了标记清除,标记清除就是循环引用的内容引用计数自建1
    4. 为了解决多次扫描一个双端链表,使用了分代回收,一共3个代 0,1,2
    5. 当0代的总长度>=700是扫描一下0代,当0代扫描10次后扫描一次1代
    6. 当1代扫描10次后扫描一次2代

     

  • 相关阅读:
    JS/JQuery下拉列表选中项的索引
    数据挖掘
    Sencha安装
    新的开始
    jquery multi scrollable 同步的问题
    dom4j
    rest
    spring 2
    spring framework3.0开发
    笔记Spring in action
  • 原文地址:https://www.cnblogs.com/Pythonzrq/p/12060324.html
Copyright © 2020-2023  润新知