zoukankan      html  css  js  c++  java
  • Python内存管理知识整理

    一切变量皆是对象的引用

    当创建对象时, Python 立即向操作系统请求内存

    可以用id(变量名)来获取该变量所引用对象的内存地址

    >>> a=1
    >>> print(id(a))
    56780120
    

    is关键字用于判断引用是否相同,==用于判断引用的内容是否相同

    >>> a={'1':1}
    >>> b={'1':1}
    >>> a==b
    True
    >>> id(a)
    44204920L
    >>> id(b)
    45830760L
    >>> a is b 
    False
    
    >>> a="123" 
    >>> b="123"
    >>> a is b
    True
    >>> id(a)
    45845320L
    >>> id(b)
    45845320L
    

    在Python中,整数和短小的字符,Python都会缓存这些对象,以便重复使用。当我们创建多个等于“123”的引用时,实际上是让所有这些引用指向同一个对象。

    引用计数

    当某个对象被创建并赋值给变量时,该对象的引用计数都被设置为1,再次被引用会增加该对象的引用计数,而当对象的引用被销毁,引用计数会减小。

    查看一个对象的引用计数:

    if __name__ == '__main__':
        from sys import getrefcount
        arr = [4,5,6,7,0,1,2]
        print(getrefcount(arr))
        # 2
    

    使用某个对象的引用作为getrefcount的参数时,此参数实际上创建了一个对象的临时引用,在Python当中,所有的传参都是引用传递。因此getrefcount返回的引用计数是该对象实际的引用计数+1

    getrefcount不仅仅统计当前代码块对对象的引用计数,还统计了import模块中对对象的引用计数。在python的内置模块中,可能有很多对数字1的引用,因此

    >>> from sys import getrefcount
    >>> getrefcount(1)
    102
    

    一个对象的引用计数变为0后,用户不可能通过任何方式动用这个对象,但是,此对象占用的内存并不会立即被回收,因为python在执行垃圾回收时会暂停其他所有任务,因此垃圾回收只会在必要的时候执行

    可以使用gc.get_count()查看gc实时计数情况验证这一点

    >>> gc.get_count()
    (560, 10, 0)
    

    560表示距离上一次0代垃圾检查,Python分配内存的数目减去释放内存的数目

    10表示距离上次1代垃圾检查,0代垃圾检查的数量

    0表示距离上次2代垃圾检查,1代垃圾检查的数量

    >>> gc.get_count()
    (560, 10, 0)
    >>> a=[1,2,3]
    >>> id(a)
    23419440
    >>> gc.get_count()
    (561, 10, 0)
    // 为对象[1,2,3]分配了内存
    
    >>> del a
    >>> gc.get_count()
    (561, 10, 0)
    // 删除引用a后,并没有立即回收[1,2,3]占用的内存
    
    >>> a=[1,2,3]
    >>> gc.get_count()
    (561, 10, 0)
    >>> id(a)
    23419440
    // 没有重新创建[1,2,3]对象,因此分配内存的数目没有增加
    // a再次引用了之前没有被回收的对象[1,2,3],它们的内存地址是一样的
    
    >>> a=[1,2,3,4]
    >>> gc.get_count()
    (562, 10, 0)
    // 为对象[1,2,3,4]分配了内存,[1,2,3]引用计数归零,但还是没有回收[1,2,3]占用的内存
    

    引用计数法最主要的缺点在于不能解决对象的循环引用问题

    循环引用

    注意:只有容器对象才会产生循环引用的情况,比如列表、字典、用户自定义类的对象、元组等。而像数字,字符串这类简单类型不会出现循环引用。

    a = { } # 变量a指向对象A,A的引用计数为 1
    b = { } # 变量b指向对象B,B的引用计数为 1
    a['b'] = b  # B的引用计数增1
    b['a'] = a  # A的引用计数增1
    del a # A的引用计数减 1,最后A对象的引用为 1
    del b # B的引用计数减 1, 最后B对象的引用为 1
    

    我们已经不能通过任何变量访问到A、B对象,但是由于它们各包含一个对方对象的引用,因此它们的引用计数无法归零,因此不会被回收。如果仅仅使用引用计数法来管理内存,则会因为循环引用造成内存泄露

    为了解决对象的循环引用问题,Python引入了标记-清除和分代回收两种GC机制。

    标记-清除

    https://andrewpqc.github.io/2018/10/08/python-memory-management/

    跟其名称一样,该算法在进行垃圾回收时分成了两步,分别是:

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

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

    标记阶段

    GC第一次遍历所有对象,复制每个对象的引用计数,可以记为gc_ref。假设,每个对象i,该计数为gc_ref_i。Python会遍历所有的对象i。对于每个对象i引用的对象j,将相应的gc_ref_j减1。这一步操作就相当于解除了循环引用对引用计数的影响。

    接着,GC第二次遍历所有的容器对象,如果对象的gc_ref值为0,那么这个对象就被标记为unreachable;如果对象的gc_ref不为0,则被标记为reachable,并且会递归地将从该节点出发可以到达的所有节点标记为reachable

    被标记为unreachable的对象会被移到Unreachable链表中

    清除阶段

    回收所有被标记为unreachable的对象

    分代回收

    在标记-清除算法执行的过程中,需要扫描整个内存空间,应用程序会被暂停,为了提升工作效率,Python采用了分代回收的策略

    弱代假说:年轻的对象通常消亡得快,而老对象则很可能存活更长时间。

    python将所有对象分为0、1、2三代,他们对应的是3个链表。

    所有新建对象都是0代,当某一代对象经历过垃圾回收,依然存活,则被归入下一代。

    如果0代经历一定次数的垃圾回收,则会启动对0代和1代的垃圾回收;当1代也经历了一定次数的垃圾回收,则会启动对0、1、2代的垃圾回收

    查看gc相关阙值:

    >>> import gc
    >>> print(gc.get_threshold())
    (700, 10, 10)
    

    700是被分配的对象与被释放的对象之差(分配内存的数目减去释放内存的数目);后面两个10,表示10次0代垃圾回收后,才会执行一次0、1代的垃圾回收;10次1代垃圾回收后,才会执行一次0、1、2代的垃圾回收

    手动触发垃圾回收:

    >>> print(gc.get_count())
    (562, 10, 0)
    >>> a={1}
    >>> print(gc.get_count())
    (563, 10, 0)
    >>> gc.collect()
    0
    >>> print(gc.get_count())
    (22, 0, 0)
    
    gc.collect(generation=2)
    若被调用时不包含参数,则启动完全的垃圾回收。可以通过generation参数指定启动哪一代的垃圾
    

    参考资料

    gc --- 垃圾回收器接口

    Python中的垃圾回收机制

    聊聊python的内存管理

    python内存分配

    Python深入06 Python的内存管理

    画说 Ruby 与 Python 垃圾回收

  • 相关阅读:
    安卓界面基本组件------计时器
    安卓界面组件----时间日期拾取器
    安卓界面组件----列表视图
    安卓组件------列表选择框
    Redis 开启远程访问
    收集的一个关于大批量插入数据的代码
    Server.MapPath和Request.PhysicalApplicationPath的异同
    C#中使用正则表达式验证电话号码、手机号、身份证号、数字和邮编
    cocos2d-x3.2在xcode6.1下的 环境搭建
    STL源码剖析(适配器)
  • 原文地址:https://www.cnblogs.com/luozx207/p/12918389.html
Copyright © 2011-2022 走看看