系统相关
首页 > 系统相关> > python的内存管理机制

python的内存管理机制

作者:互联网

内存管理机制

内存的管理:分配(malloc)+回收(free)

作用:控制python内存,对python内存进行回收

  1. python中一切皆对象,python的存储就是分配内存空间去存储对象

  2. 整数和短小的字符(基本就是一个单词)使用的是缓存机制,以便快速重复使用

  3. 使用is检验是否为同一个对象

三个方面:引用计数+垃圾回收+内存池

引用计数机制

引用计数机制:⭐️

  1. 记录对象被引用的次数

  2. 优点:

    1. 简单

    2. 实时性:对象一旦没有引用,内存直接释放

  3. 缺点:

    1. 维护引用计数消耗资源

    2. 解决不了循环引用问题

  4. 什么时候引用计数增加?

    1 对象创建a=1
    2 对象引用b=a
    3 对象作为参数传递func(a)
    4 对象存储在容器中1=[a]
  5. 什么时候引用计数减少?

    1 显示使用del a
    2 引用指向了别的对象b=None
    3 离开了对象的作用域(比如函数执行结束)
    4 从一个容器移除对象或者销毁容器
  6. 查看引用计数

    1 import sys
    2 a = [1,2]
    3 sys.getrefcount(a)      #查看引用计数,getrefcount 本身也会引入一次计数
    4 #传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。

垃圾回收机制(GC:Garbage Collection)

作用:在python解释器中,会自动回收没有用的内存

功能:

  1. 为新对象分配内存

  2. 识别垃圾对象

  3. 从垃圾对象那回收内存

实现: 引用计数为主 + 标记清除和分代回收为辅

  1. 引用计数为0时自动回收

  2. 标记清除算法:⭐️

    1. 用图论来理解不可达的概念。对于一个有向图,如果从一个节点出发进行遍历,并标记其经过的所有节点;在遍历结束后,所有没有被标记的节点,称之为不可达节点。不可达节点的存在是没有任何意义的,需要对它们进行垃圾回收。

    2. 每次都遍历全图,一种巨大的性能浪费。所以,标记清除算法使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类对象才有可能产生循环引用)。

  3. 分代收集算法:⭐️

    1. 将 Python 中的所有对象分为三代。刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。

  4. 触发垃圾回收机制的情况

    1. gc.collect():主动回收

    2. gc模块的计数器达到3个阈值时

    3. 程序退出时

    1 # 手动释放内存
    2 import gc
    3 del a
    4 gc.collect()    # 主动回收
  5. GC中的方法

    1 gc.garbage  # 垃圾回收后的对象会放在这里
    2 gc.get_count()  # 返回长度为3的列表,分别表示第0代对象,第1代对象和第2代对象 值
    3 gc.get_threshold()  # 返回长度为3的列表,分别表示第0代对象,第1代对象和第2代对象的 阀值,通常时(700,10,10)
    4 # 每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。
    5 gc.set_threshold()  # 设置第0代、第1代、第2代对象的 阈值 比如:gc.set_threshold(700,10,5)
    6 gc.collect([第几代]) # 也可以输入0,1,2,0:只检查第一代,1:只检查第一代和第二代,2:检查3代
    7 gc.disable

内存池机制(Pymalloc)

python中有大内存和小内存之分(以256KB为界限)

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

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

内存池

当创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。

内存池的概念就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率

第3层:最上层,用户对Python对象的直接操作

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

第0层:大内存-----若请求分配的内存大于256K,malloc函数分配内存,free函数释放内存。

第-1,-2层:操作系统进行操作

内存泄漏

  1. 原因:

    1. 所用到的用 C 语言开发的底层模块中出现了内存泄露。

    2. 代码中用到了全局的 list、 dict 或其它容器,不停的往这些容器中插入对象,而忘记了在使用完之后进行删除回收

    3. 代码中有“引用循环”,并且被循环引用的对象定义了del方法,就会发生内存泄露。

  2. 诊断思路:python 对象在不停的增长;因此,首先是要找到这些异常的对象。

  3. 诊断步骤

    1. 工具:

      1. gc模块

      2. objgraph模块(第三方模块,用于诊断内存问题)

    2. 在服务程序的循环逻辑中,选择出一个诊断点

    3. 在诊断点插入如下语句

      1 import gc
      2 import objgraph
      3 ​
      4 # 强制进行垃圾回收
      5 gc.collect()
      6 ​
      7 # 打印出对象数目最多的50个类型信息
      8 objgraph.show_most_common_types(limit=50)

内存溢出

  1. 原因:

    1. 内存中加载的数据量过于庞大

    2. 集合类中有对对象的引用,使用完后未清空,产生了堆积,使得JVM不能回收

    3. 代码中存在死循环或循环产生过多重复的对象实体

    4. 使用的第三方软件中的BUG

    5. 启动参数内存值设定的过小

  2. 解决方案:

    1. 修改JVM启动参数,直接增加内存(-Xms,-Xmx参数一定不要忘记加)

    2. 检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误

    3. 对代码进行走查和分析,找出可能发生内存溢出的位置

      重点排查以下几点:

      1. 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

      2. 检查代码中是否有死循环或递归调用。

      3. 检查是否有大循环重复产生新对象实体。

      4. 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

    4. 使用内存查看工具动态查看内存使用情况

  3. 与内存泄漏的区别

  4. 内存溢出是指向JVM申请内存空间时没有足够的可用内存了,就会抛出OOM即内存溢出。

    内存泄漏是指,向JVM申请了一块内存空间,使用完后没有释放,由于没有释放,这块内存区域其他类加载的时候无法申请,

    同时当前类又没有这块内存空间的内存地址了也无法使用,相当于丢了一块内存,这就是内存泄漏。

    值得注意的是内存泄漏最终会导致内存溢出,很好理解,内存丢了很多最后当然内存不够用了

标签:垃圾,python,管理机制,回收,对象,gc,内存,引用
来源: https://www.cnblogs.com/totopian/p/15245320.html