引用计数器

      在refchain中的所有对象内部都有一个ob_refcnt用来保存当前对象的引用计数器,顾名思义就是自己被引用的次数.

      在python中创建的任何对象都会加入到refchain中.refchain是一个环状双向链表数据结构.refchain包含了python中所有的对象
      refchain链表中的每个元素都是由C源码创建的一个struct

      在C源码中有两个关键的结构体: PyObject和PyVarObject
      这两个结构体PyObject和PyVarObject是基石,他们保存这其他数据类型公共部分

      每个类型的对象在创建时都有PyObject中的那4部分数据. list/set/tuple等由多个元素组成对象创建时都有PyVarObject中的那5部分数据

      在python中创建任何一个变量底层C源码就会创建一个对应的struct实例 这就是python中一切皆对象的原理

 

python的内存管理和垃圾回收_引用计数

    每一个实例中都有一个obj_refcnt属性 这个属性表示的就是当前实例的引用数量.创建任何一个struct实例的时候obj_refcnt的值都会被赋值为1,当有其他变量引用此对象的时候obj_refcnt的值就会发生变化.当refchain链表中元素的obj_refcnt的值为0的时候,这个元素就会从refchain链表中移除
    当一个实例的obj_refcnt变成0的时候,这个元素要么被移动到free_list链表中,要么被回收掉.

标记清除和分代回收

        基于引用计数器进行垃圾回收非常方便和简单,但是存在循环引用的问题.基础类型的变量不存在循环引用,只有容器类型的数据才会出现循环引用
        循环引用的问题,他们的引用计数器不为0所以他们的状态:永远不会被使用、也不会被销毁.项目中如果这种代码太多,就会导致内存一直被消耗,直到内存被耗尽程序崩溃

        

python的内存管理和垃圾回收_python_02

        上述代码会发现,执行del操作之后,没有变量再会去使用那两个列表对象.但由于循环引用的问题,他们的引用计数器不为0,所以他们的状态:永远不会被使用也不会被销毁

   标记清除:

         创建特殊链表专门用于保存列表,元组,字典,集合,自定义类等对象,之后再去检查这个链表中的对象是否存在循环引用,如果有则让双方的引用计数都减1

   分代回收:

        对标记清除中的链表进行优化,将那些可能存在循引用的对象拆分到3个链表.链表称为:0/1/2三代,每代都可以存储对象和阈值,当达到阈值时,就会对相应的链表中的每个对象做一次扫描,除循环引用各自减1并且销毁引用计数器为0的对象

      

python的内存管理和垃圾回收_python_03

标记清除的实现原理

           

python的内存管理和垃圾回收_引用计数_04

 

          

python的内存管理和垃圾回收_引用计数_05

 

           在python内部机制中 当满足某个条件会自动触发扫描0代链表中的每个元素

          如果有循环引用则把双方元素的引用计数器都减去1.各自引用计数减1后如果变成0则进行垃圾回收。如果大于0则把元素从当前代链表移动到更高级别的分代链表。如: 从0代移动到1代或者从1代移动到2代

          什么时候触发扫描

                   新创建对象使generations的0代链表上的对象数量大于阈值700时,要对链表上的对象进行扫描检查   

                   0代      0代中对象个数达到700个时候扫描1次
                   1代      0代链表扫描10次后1代链表扫描1次
                   2代      1代链表扫描10次后2代链表扫描1次

          扫描链表的代价较大

                    将可能存在循环引用元素的链表分为三个双向链表来存储

      标记阶段
             遍历所有的对象,如果是可达的(reachable)也就是还有对象引用它,那么就标记该对象为可达
      清除阶段
              再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收

      在标记清除算法中,为了追踪容器对象,需要每个容器对象维护两个额外的指针,用来将容器对象组成一个双端链表,指针分别指向前后两个容器对象python 解释器 (Cpython) 维护了两个这样的双端链表,一个链表存放着需要被扫描的容器对象,称为 Object to Scan,另一个链表存放着临时不可达对象,称为 Unreachable

     找出循环引用的元素步骤

    

python的内存管理和垃圾回收_链表_06

 

 

     

python的内存管理和垃圾回收_链表_07

 

      

python的内存管理和垃圾回收_python_08

 

     

python的内存管理和垃圾回收_链表_09

缓存机制(内存优化)

    缓存池机制

     为了避免重复的创建和销毁一些常见对象,为每个基础类型实例维护一个池子
     在python解释器启动的时候,会在内部自动创建-5到257常见整数的strut实例。重复使用small_ints(小数据池)范围的整数时候不会重新开辟内存空间
     v1=7 内部不会开辟内存,直接去缓存池中去取。 v2=1000 不在缓存池中 会重新开辟内存空间
     char类型维护unicode_latin1[256]链表, 每个ascii字符码也会在内部自动缓存起来,以后使用时就不再反复创建

    在缓存池中的这些元素的引用计数永远不会为0,最少也会是1.所以永远不会被回收

  free_list机制

     当一个对象的引用计数器为0的时候,理论上应该进行回收。但是python不会直接回收,而是将此对象添加到free_list链表中当做缓存。以后再次创建相同类型的实例的时候不再重新开辟内存而是直接使用free_list中某个元素,对元素对象重新初始化属性,然后存入refchain中

    只有当计数为0而且free_list元素个数已经占满的情况下才会对对象进行回收

python的内存管理和垃圾回收_python_10

字符串驻留机制

       如果一个字符串只包含字母,数字,下划线并且长度不大于20的话 python会把这个字符串保存在内存中.下次创建同样内容的字符串时候会直接使用原来的字符串内存地址