zoukankan      html  css  js  c++  java
  • Python内存管理机制

    Python内存管理机制

    一.变量与对象

    由于python中万物皆对象,所以python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它

    变量

    • 通过变量指针引用对象

    • 变量指针指向具体对象的内存空间,取对象的值

    对象

    • 类型已知
    • 每个对象都包含一个头部信息(头部信息:类型标识符引用计数器

    注意:

      变量名没有类型,类型属于对象(因为变量引用对象,所以类型随对象),变量引用什么类型的对象,变量就是什么类型的。

    a = 123
    b = a
    print(id(a))
    print(id(b))
    
    a = 456
    print(id(a))
    print(id(b))
    

    引用所指判断

    通过is进行引用所指判断,is是用来判断两个引用所指的对象是否相同

    整数:

    a = 1
    b = 1
    print(a is b)  # True
    

    短字符串:

    c = "good"
    d = "good"
    print(c is d)  # True
    

    长字符串:

    e = "very good"
    f = "very good"
    print(e is f)  # False
    

    列表:

    g = []
    h = []
    print(g is h) # False
    

    由运行结果可知:

    1. Python缓存了整数和短字符串,因此每个对象在内存中只存有一份,引用所指对象就是相同的,即使使用赋值语句,也只是创造新的引用,而不是对象本身;
    2. Python没有缓存长字符串、列表及其他对象,可以由多个相同的对象,可以使用赋值语句创建出新的对象。

    二.引用计数机制

    在Python中,每个对象都有指向该对象的引用总数---引用计数

      查看对象的引用计数:sys.getrefcount()

    普通引用

    import sys
    
    
    a = [1, 2, 3]
    print(sys.getrefcount(a))  # 2
    
    b = 2
    print(sys.getrefcount(a))  # 3
    print(sys.getrefcount(b))  # 3
    

    注意:

      当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。getrefcount()所得到的结果,会比期望的多1。

    容器对象

    Python的一个容器对象(比如:表、词典等),可以包含多个对象

    a = [1, 2, 3, 4, 5]
    b = a
    print(a is b)  # True
    a[0] = 6
    print(a)  # [6, 2, 3, 4, 5]
    print(a is b)  # True
    print(b)  # [6, 2, 3, 4, 5]
    

    由上可见,实际上,容器对象,如列表、元组、字典等,存储的其它对象,仅仅是对其它对象的引用,即地址,并不是这些对象本身

    引用计数增加

    1. 对象被创建,引用计数增加1
    2. 对象被引用,增加1
    3. 作为容器的对象,增加1
    4. 对象被当作参数传入函数func(object),增加2
    import sys
    
    
    # 对象被创建
    obj = "I love Python, I want to be a good programmer!"
    print(sys.getrefcount(obj) - 1)  # 3
    
    # 被引用
    m = obj
    print(sys.getrefcount(obj) - 1)  # 4
    p = m
    print(sys.getrefcount(obj) - 1)  # 5
    
    # 作为容器对象的一个元素
    a = ["a", "b", obj]
    print(sys.getrefcount(obj) - 1)  # 6
    
    
    def foo(x):
        z = "Hello world! "
        print(sys.getrefcount(obj) - 1)  # 8
        print(z)
    
    
    foo(obj)
    print(sys.getrefcount(obj) - 1)  # 6  函数执行完毕后局部名称空间被销毁,引用计数减少
    

    引用计数减少

    1. 对象的别名被显式减少 del obj
    2. 对象的一个别名被赋值给其它对象
    3. 对象从一个容器对象中移除,或,容器对象本身被销毁
    4. 一个本地引用离开了它的作用域,比如上面的foo(x)函数结束时,x指向的对象引用减1

    引用计数器机制

    作用:

    • 利用引用计数器方法,Python解释器在检测到对象引用个数为0时,对普通的对象进行释放内存的机制

    关于循环引用问题

    • 循环引用即对象之间进行相互引用,出现循环引用后,利用上述引用计数机制无法对循环引用中的对象进行释放空间,这就是循环引用问题

    • 循环引用形式

      import sys
      
      
      class Person(object):
          pass
      
      
      class Dog(object):
          pass
      
      
      p = Person()
      d = Dog()
      print(sys.getrefcount(p)-1, sys.getrefcount(d) - 1)  # 1, 1
      p.pet = d
      print(sys.getrefcount(d) - 1)  # 2
      d.master = p
      print(sys.getrefcount(p) - 1)  # 2
      

      object p中的属性引用object d, 而object d中的属性同时引用object p,从而造成仅仅删除对象d和p,无法释放内存空间,因为它们依然被引用;

      即,循环引用后,对象p和d的引用计数为2,删除对象p和d后,引用计数为1,不是0,而Python解释器只有在检查到一个对象的引用计数为0时,才能自动释放内存,所以这里无法释放p和d所占用的内存


    垃圾回收机制

    当Python中的对象越来越多,占据越来越大的内存,启动垃圾回收garbage collection,将没用的对象清除

    作用:

    • 从经过引用计数器机制后还没有被释放掉内存的对象中,找到循环引用对象,并释放掉其内存

    底层层面(原理)

    垃圾回收检测流程:

    如何找到循环引用并释放内存:

    1. 收集所有容器对象(循环引用只针对容器对象,其它对象不会产生循环引用),使用双向链表(可以看作一个集合)对这些容器对象进行引用
    2. 针对每一个容器对象,使用变量gc_refs来记录当前对应的引用个数
    3. 针对每一个容器对象,找到其正在引用的其它容器对象,并将这个被引用的容器对象引用计数减去1
    4. 检查所有容器对象的引用计数,若为0,则证明该容器对象是由于循环引用存活下来的,并对其进行销毁

    提升查找循环引用的性能:

    • 循环引用查找和销毁过程非常繁琐,要分别处理每一个容器对象,所以python考虑一种改善性能的做法,即分代回收

    • 首先是一个假设--如果一个对象被检测了10次还没有被销毁,就减少对其的检测频率;基于这个假设,提出一套机制,即分代回收机制

    分代回收机制:

    1. 默认一个对象被创建出来后,属于0代
    2. 如果经历过这一代“垃圾回收”后,依然存活,则划分为下一代
    3. “垃圾回收”的周期顺序为
      1. 0代“垃圾回收”一定次数,会触发0代和1代回收
      2. 1代“垃圾回收”一定次数,会触发0代,1代和2代回收

    通过这个分代回收机制,循环引用处理过程就会得到很大的性能提升

    垃圾回收时机

    自动回收:

    手动回收:这里要使用gc模块中的collect()方法,使得执行这个方法时执行分代回收机制

    import objgraph, gc, sys  # objgraph模块的count()方法是记录当前类产生的实例对象的个数
    
    
    class Person(object):
        pass
    
    
    class Dog(object):
        pass
    
    
    p = Person()
    d = Dog()
    print(sys.getrefcount(p) - 1, sys.getrefcount(d) - 1)  # 1, 1
    p.pet = d
    d.master = p
    print(sys.getrefcount(p) - 1, sys.getrefcount(d) - 1)  # 2, 2
    del p, d
    print(objgraph.count("Person"), objgraph.count("Dog"))  # 1, 1
    gc.collect()
    print(objgraph.count("Person"), objgraph.count("Dog"))  # 0, 0
    

    内存池机制

    Python中有分为大内存和小内存:(256K为界限分大小内存)

    1. 大内存使用malloc进行分配
    2. 小内存使用内存池进行分配

    Python的内存(金字塔):

    • 第3层:最上层,用户对Python对象的直接操作
    • 第1层和第2层:内存池,由Python的接口函数PyMem_Malloc实现-----若请求分配的内存在1~256字节之间就使用内存池管理系统进行分配,调用malloc函数分配内存,但是每次只会分配一块大小为256K的大块内存,不会调用free函数释放内存,将该内存块留在内存池中以便下次使用
    • 第0层:大内存-----若请求分配的内存大于256K,malloc函数分配内存,free函数释放内存
    • 第-1,-2层:操作系统进行操作

    总结

    python的内存管理机制就是引用计数器机制和垃圾回收机制的混合机制

  • 相关阅读:
    VC++菜单
    VC++的菜单控制和自绘菜单
    windowsUI的总结
    Linux mount BSD disk partition
    Linux qemu-nbd mount qemu disk image
    自定义chromium浏览器
    EF6配合MySQL或MSSQL(CodeFirst模式)配置指引
    使用 dmidecode 查看Linux服务器信息
    修改KVM的模拟网卡类型
    华为TaiShan 2280 ARM 服务器
  • 原文地址:https://www.cnblogs.com/pankypan/p/11098536.html
Copyright © 2011-2022 走看看