• python内存管理&垃圾回收


    python内存管理&垃圾回收

    1595916309706

    引用计数器

    1. 环装双向列表refchain

      在python程序中创建的任何对象都会放在refchain连表中

      name = '张三'
      age = 18
      hobby = ['汽车','游艇']
      

      创建一个变量!内部会创建一些数据【上一个对象,下一个对象,类型,引用个数

      name = '张三'
      内部会创建一些数据【上一个对象,下一个对象,类型,引用计数】
      age = 18
      内部会创建一些数据【上一个对象、下一个对象、类型、引用计数、val=18】
      hobby = ['汽车','游艇']
      内部会创建一些数据【上一个对象、下一个对象、类型、引用计数、items=元素】
      
    #define Pyobject_HEAD
    #define Pyobject_VAR_HEAD
    
    //宏定义,包含 上一个、下一个,用于构造双向连表用(放到refchain链表中,要用到)
    #define _PyObject_HEAD_EXTRA
    	struct _object * _ob_next;
    	struct _object * _ob_prev;
    
    typedef struct _object {
        _PyObject_HEAD_EXTRA//构造双向连表
        Py_ssize_t ob_refcnt;//引用计数器
        struct _typeobject * ob_type//数据类型
    }PrObject;
    
    typedef struct {
        PyObject ob_base;//PyObject对象
        
        Py_ssize_t ob_size;//Number of items in variable part 元素个数
       
    }PyVarObject;
    

    在C源码中如何体验每个对象都有相同的值:PyObject结构体(4个值)

    有多个元素组成的对象:PyObject结构体(4个值)+ob_size

    类型封装结构体

    data = 3.14
    内部会创建:
    	_ob_next refchain中的上一个对象
    	_ob_prev refchain中的下一个对象
    	_ob_refcnt = 1
    	_ob_type = float
    	_ob_fval = 3.14
    

    引用计数器

    v1 = 3.14
    v2 = 999
    v3 = (1,2,3)
    
    

    当python程序运行时,会根据数据类型的 不同找到其对应类型的结构体,根据结构体中的字段来创建相关的数据,然后将对象添加到refchain双向链表中。

    在C源码中有两个关键的结构体:PyObject、PyVarObject

    每个对象这种有ob_refcnt就是引用计数器,值 默认为1,当有其他变量引用对象时,引用计数器就会发生变化。

    #引用
    a = 666
    b = a  引用计数2
    
    a = 666
    b = a
    del b  #b变量删除:b对应对象引用计数器-1
    
    当一个对象的引用计数器为0时,意味着没有使用这个对象,这个对象就是垃圾。会被垃圾回收。
    #回收:1.当对象从refchain链表移除,2.将对象销毁。内存归还。
    

    引用计数器的BUG

    循环引用的问题

    1595918672956

    标记清除

    目的:为了解决引用计数器循环引用的不足
    实现:在python的底层,维护一个链表,链表中专门放可能存在循环引用的对象【tuple、list、dict、set】。

    在python内部某种情况下触发,回去扫描 可能存在循环应用的链表中的每个元素,检查是否有循环引用,如果有引用,让双方的引用技术器-1,如果是0 则垃圾回收。

    问题:什么时候扫描?

    可能存在循环引用的链表扫码代价大!每次扫描耗时时间长。

    分代回收

    1595919202048

    将可能存在循环引用的对象维护成3个链表

    • 0代:0代中对象个数达到700个扫描一次。
    • 1代:0代扫描10次,则一代扫描1次。
    • 2代:1代扫描10次,则2代扫描1次。

    小结

    在python中维护了一个refchain的双向环装链表,这个链表中存储程序创建的所有对象,每种没醒的对象都有一个_obj_refcnt引用计数器的值,引用个数+1、-1,最后当引用计数器变为0时会进行垃圾回收(对象销毁、refchain中移除).

    但是,在python对于那些可以有多个元素组成的对象可能会存在循环引用的问题,为了解决这个问题python又引入了标记清除和分代回收,在其内部都为4个链表。

    • refchain
    • 2代
    • 1代
    • 0代

    在源码中当达到各自的阈值时,,就会触发扫描器进行标记清除的动作(有循环各自-1)

    但是,源码内部在上述的流程中提出了优化机制。

    python缓存

    为了避免重复创建和销毁一些常见对象、维护池。

    v1 = 7
    v2 = 3
    v3 = 3
    

    为了节省资源、python只会创建两个资源

    池的范围在 -5、 -4 到257

    当数据大于257的时候,python会单独在内存中创建资源。

    池(int、字符串)

    free_list

    • 当一个对象的引用计数器为0时,按道理讲应该会回收,但是在python中不会直接回收,而是将对象添加到free_list链表中当缓存,以后再去创建对象时,不再重新开辟内存,而是直接使用free_list.

      v1 = 3.14  开辟内存,内部存储结构中定义那几个值,并存到refchain中
      del v1 refchain中移除,将对象添加到free_list中【80个】,free_list满了才销毁。
      v9 = 9999 不会重新开辟内存,去free_list中获取对象,对象内部数据初始化,再放到refchain中
      
  • 相关阅读:
    mysql分表场景分析与简单分表操作
    Linux内嵌汇编
    window 和 linux x64差别
    sourcetree和gitlab配置图解
    QT如何管理组件(解决“要继续此操作,至少需要一个有效且已启用的储存库”问题)
    QT5.x应用在Mac OS X和Windows平台的发布过程
    python中读写二进制文件
    mysql分表的3种方法
    MySQL-C++封装类
    MySQL删除数据库时无响应解决办法
  • 原文地址:https://www.cnblogs.com/pyliuwei/p/13391322.html
Copyright © 2020-2023  润新知