zoukankan      html  css  js  c++  java
  • python内存管理、垃圾回收机制(总结)

    内存管理机制:引用计数、垃圾回收、内存池机制

    1.变量与对象
    变量:通过变量指针引用对象,变量指针指向具体对象的内存地址,最终这个变量取的是对象的值
    个人理解:变量中保存的是数据地址,这里的对象即是指数据

    对象:类型已知,每个对象都包含头部信息
    头部信息中存的是对象的类型标识符和引用计数器 # 对象即数据 类型即数据类型 引用计数即有多少变量指向这个数据
    变量名是没有类型的,平时得到的变量类型都是对象的类型,因为变量是引用的对象


    引用所指:通过is判断两个引用所指的是否是同一对象
    比如:
    a = 1
    b = a
    print(a is b) #结果是true,a和b的地址相同

    关于引用:
    1)python缓存了整数和短字符串,整数和短字符串创建后在内存中只有一份,之后如果再创建相同的整数或短字符串,实际上都指向第一次创建的这个整数和短字符串

    比如:
    a = 1
    b = 1
    print(a is b) #结果是true,a和b的地址相同


    2)长字符串、列表等其他对象,通过赋值语句,可以创建多个新的相同对象【数据的值相同,数据地址不同】

    3)长字符串和短字符串的区分:由数字字母下划线组成,不含其他字符的,就是短字符串,其他是长字符串

    2.引用计数
    python中,每个对象都有指向该对象的引用总数--引用计数
    查看对象的引用计数:sys.getrefcount()

    1)引用计数增加:
    1-1)赋值语句会创建一个引用。
    前提是这个数据之前没有被创建 或者这个数据之前被创建了 但不是整数和短字符串

    1-2)给函数传参会增加一个引用,比如
    a = "123"
    len(a),a把“123”的数据地址传递给了函数内部的参数,所以“123”会增加一个引用计数

    1-3)作为列表或字典的元素,对象的引用会增加
    前提是这个元素是一个整数或者短字符
    增加的意思是,在这之前对象已被创建
    比如:
    a = 1 # 创建了对象,引用计数为1
    b = [1, 2, 3] # 列表中使用了1, 1的引用计数增加1个,变成2


    2)引用计数减少:

    2-1)对象的别名被显示销毁。别名指的是变量
    比如
    a=1
    del a

    2-2)对象的别名改变了引用
    比如
    a = 1 # 1的引用为1
    b = a # 1的引用为2
    b = 3 # b的变成了对3的引用,1的引用计数减少1

    2-3)对象从一个窗口对象中移除,或者窗口对象本身被销毁

    a = [1, 2, 123] # 假设这里第一次创建123,引用计数为1
    b = 123 # 123的引用计数+1 变成2
    a.remove(123) # 123的引用计数减少1 恢复为1

    2-4)一个本地引用离开了它的作用域
    比如需要传参的函数,在函数执行完以后,参数所指向的数据,它的引用计数会减少1

    3.垃圾回收

    当python中的对象原来越多时,占据越来越多内存,会启动垃圾回收,清除没用的对象

    1)原理
    当python某个对象的引用计数变为0后,说明没有任何引用指向该对象,该对象就需要被回收。

    当垃圾回收启动的时候,python扫描到这个对象的引用计数为0,就会把这个数据清空,腾出内存空间

    2)垃圾回收何时启动
    启动垃圾回收时,python不能进行其他的任务,所以不能进行太频繁的垃圾回收

    python只在特定条件下自动启动垃圾回收

    当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动

    In [93]: import gc

    In [94]: gc.get_threshold()  #gc模块中查看阈值的方法
    Out[94]: (700, 10, 10)


    3)阈值分析:

      700即是垃圾回收启动的阈值;

      每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收;

    当然也是可以手动启动垃圾回收: 

    In [95]: gc.collect() #手动启动垃圾回收
    Out[95]: 2


    4)分代回收

    Python将所有的对象分为0,1,2三代;

    所有的新建对象都是0代对象;

    当某一代对象经历过垃圾回收,依然存活,就被归入下一代对象。


    3.1垃圾回收的理解
    1)正常情况下垃圾回收启动时,如果扫描到对象的引用计数为0,那么就会清除该对象,回收这个对象占据的内存空间

    2)特殊情况:存在循环引用导致有些内存没有办法回收。如下

    a = ["1"] # a列表的引用计数为1
    b = ["2"] # b列表的引用计数为1
    a.append(b) # b列表的引用计数为2,a=["1", b]
    b.append(a) # a列表的引用计数为2,b=["1", a]
    del a # a的引用计数为1
    del b # b的引用计数为1

    执行了del a 和del b,两个变量名 都被删除了,很显然想要的结果是直接让这两个列表的引用变为0,可以进行回收
    但是变量名a b 虽然没有了,可a列表和b列表的引用计数却都还剩1,达不到回收这两个列表的要求
    而外界已没有指向这两个列表的变量名了,外界无法访问,则无法再通过del去把这两个列表的引用计数变为0
    导致这两个内存无法回收【内存泄漏】

    为了解决循环引用带来的问题,有了分代回收机制

    3)分代回收机制
    分代回收中,如果对象的引用计数为0,那么它所占的空间就会被python解释器回收


    第一种理解:根据对象的权重把对象移入更高级的一代

    分代回收的核心思想是:在多次扫描的情况下,都没有被回收的变量,GC机制就会认为,该变量是常用变量,GC对其扫描的频率会降低

    具体实现原理如下:

    分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)

    新创建的对象,放到新生代这个等级中,假设每隔1分钟扫描新生代一次,如果发现对象依然被引用,那么该对象的权重(权重本质就是个整数)加一, 当对象的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代,
    青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次GC需要扫描的变量的总个数就变少了,节省了扫描的总时间
    接下来,青春代中的对象,也会以同样的方式被移动到老年代中。也就是等级(代)越高,被垃圾回收机制扫描的频率越低


    第二种理解:每一代中的对象达到一定数量时进行扫描

    a.python中每创建一个对象,就会把对象添加到一个特殊的“链表”,这个链表称为“零代链表”
    当“零代链表”中的对象个数达到一定阈值,python解释器就会对这个“零代链表”进行一次“扫描”,查找列表中是否存在循环引用【互相引用】的对象,如果发现有互相引用的对象,就把这些对象的引用计数-1
    此时,如果某些对象的引用计数变为0,python解释器就会回收其内存空间,如果对象的引用计数仍不为0,就把此时存活的对象迁移到下一代列表中

    b.同样,python解释器会在一定条件下扫描“一代链表”,判断是否存在相互引用,存在就把这些对象的引用计数-1
    此时如果某些对象的引用计数变为0,就回收对应的内存,如果对象的引用计数仍然不为0,就把对象移到“二代链表”中

    c.扫描“二代链表”的操作同理,最后对象的引用计数仍不为0,则会把存活的对象再迁移到一个新的特殊内存空间

    此时重新进行零代链表-》一代链表-》二代链表的循环扫描


    4)标记-清除机制
    为了解决误删的情况,比如
    a = ["1"] # a列表的引用计数为1
    b = ["2"] # b列表的引用计数为1
    a.append(b) # b列表的引用计数为2,a=["1", b]
    b.append(a) # a列表的引用计数为2,b=["1", a]
    del a # a的引用计数为1

    在进行分代回收扫描的时候,发现a列表和b列表存在相互引用的情况,于是对这两个列表的引用计数都进行减1
    结果a列表的引用计数变为了0,然后a列表的内存被回收了,b列表未被回收,但是b列表中的元素还有对a列表的引用
    此时a列表已经被回收,导致b列表中的a元素没有用了,所以实际上此时不应回收a列表的内存,于是有了“标记-清除”机制


    “标记-清除”机制:
    a.检测链表中相互引用的对象,让它们的引用计数-1
    b.然后把所有对象分为两组:死亡组 存活组
    引用计数为0的对象进入死亡组
    引用计数不为0的进入存活组
    c.此时分析存活组,如果对象存活,则其内部对象也必须存活
    如果发现内部对象死亡,就想办法让其复活,这样就解决了误删的问题


    5)小结:
    python采用的引用计数为主,分代回收和标记清除为辅的垃圾回收机制,三者共同维护python程序占用的内存空间


    4.内存机制

  • 相关阅读:
    企业微信的部门长度问题
    MVC中view与controller传json数据
    jQuery.extend()、jQuery.fn.extend()扩展方法示例详解
    程序员成长思维:把自己当做产品来发展
    发展你的兴趣,而不是跟随你的兴趣
    领导力:不要做个“好人”
    Nginx性能优化
    【.NET与树莓派】上手前的一些准备工作
    php curl时遇到Can't load the certificate "..." and its private key: OSStatus -25299的问题
    ASCII码字符对照表
  • 原文地址:https://www.cnblogs.com/come202011/p/13356765.html
Copyright © 2011-2022 走看看