python的内存管理机制
作者:互联网
内存管理机制
内存的管理:分配(malloc)+回收(free)
作用:控制python内存,对python内存进行回收
-
python中一切皆对象,python的存储就是分配内存空间去存储对象
-
整数和短小的字符(基本就是一个单词)使用的是缓存机制,以便快速重复使用
-
使用is检验是否为同一个对象
三个方面:引用计数+垃圾回收+内存池
引用计数机制
引用计数机制:⭐️
-
记录对象被引用的次数
-
优点:
-
简单
-
实时性:对象一旦没有引用,内存直接释放
-
-
缺点:
-
维护引用计数消耗资源
-
解决不了循环引用问题
-
-
什么时候引用计数增加?
1 对象创建a=1 2 对象引用b=a 3 对象作为参数传递func(a) 4 对象存储在容器中1=[a]
-
什么时候引用计数减少?
1 显示使用del a 2 引用指向了别的对象b=None 3 离开了对象的作用域(比如函数执行结束) 4 从一个容器移除对象或者销毁容器
-
查看引用计数
1 import sys 2 a = [1,2] 3 sys.getrefcount(a) #查看引用计数,getrefcount 本身也会引入一次计数 4 #传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。
垃圾回收机制(GC:Garbage Collection)
作用:在python解释器中,会自动回收没有用的内存
功能:
-
为新对象分配内存
-
识别垃圾对象
-
从垃圾对象那回收内存
实现: 引用计数为主 + 标记清除和分代回收为辅
-
引用计数为0时自动回收
-
标记清除算法:⭐️
-
用图论来理解不可达的概念。对于一个有向图,如果从一个节点出发进行遍历,并标记其经过的所有节点;在遍历结束后,所有没有被标记的节点,称之为不可达节点。不可达节点的存在是没有任何意义的,需要对它们进行垃圾回收。
-
每次都遍历全图,一种巨大的性能浪费。所以,标记清除算法使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类对象才有可能产生循环引用)。
-
-
分代收集算法:⭐️
-
将 Python 中的所有对象分为三代。刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。
-
-
触发垃圾回收机制的情况
-
gc.collect():主动回收
-
gc模块的计数器达到3个阈值时
-
程序退出时
1 # 手动释放内存 2 import gc 3 del a 4 gc.collect() # 主动回收
-
-
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为界限)
-
大内存使用malloc进行分配
-
小内存使用内存池进行分配
内存池
当创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。
内存池的概念就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率
第3层:最上层,用户对Python对象的直接操作
第1层和第2层:内存池,有Python的接口函数PyMem_Malloc实现-----若请求分配的内存在1~256字节之间就使用内存池管理系统进行分配,调用malloc函数分配内存,但是每次只会分配一块大小为256K的大块内存,不会调用free函数释放内存,将该内存块留在内存池中以便下次使用。(内存小于256K)
第0层:大内存-----若请求分配的内存大于256K,malloc函数分配内存,free函数释放内存。
第-1,-2层:操作系统进行操作
内存泄漏
-
原因:
-
所用到的用 C 语言开发的底层模块中出现了内存泄露。
-
代码中用到了全局的 list、 dict 或其它容器,不停的往这些容器中插入对象,而忘记了在使用完之后进行删除回收
-
代码中有“引用循环”,并且被循环引用的对象定义了del方法,就会发生内存泄露。
-
-
诊断思路:python 对象在不停的增长;因此,首先是要找到这些异常的对象。
-
诊断步骤
-
工具:
-
gc模块
-
objgraph模块(第三方模块,用于诊断内存问题)
-
-
在服务程序的循环逻辑中,选择出一个诊断点
-
在诊断点插入如下语句
1 import gc 2 import objgraph 3 4 # 强制进行垃圾回收 5 gc.collect() 6 7 # 打印出对象数目最多的50个类型信息 8 objgraph.show_most_common_types(limit=50)
-
内存溢出
-
原因:
-
内存中加载的数据量过于庞大
-
集合类中有对对象的引用,使用完后未清空,产生了堆积,使得JVM不能回收
-
代码中存在死循环或循环产生过多重复的对象实体
-
使用的第三方软件中的BUG
-
启动参数内存值设定的过小
-
-
解决方案:
-
修改JVM启动参数,直接增加内存(-Xms,-Xmx参数一定不要忘记加)
-
检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误
-
对代码进行走查和分析,找出可能发生内存溢出的位置
重点排查以下几点:
-
检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
-
检查代码中是否有死循环或递归调用。
-
检查是否有大循环重复产生新对象实体。
-
检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
-
-
使用内存查看工具动态查看内存使用情况
-
-
与内存泄漏的区别
-
内存溢出是指向JVM申请内存空间时没有足够的可用内存了,就会抛出OOM即内存溢出。
内存泄漏是指,向JVM申请了一块内存空间,使用完后没有释放,由于没有释放,这块内存区域其他类加载的时候无法申请,
同时当前类又没有这块内存空间的内存地址了也无法使用,相当于丢了一块内存,这就是内存泄漏。
值得注意的是内存泄漏最终会导致内存溢出,很好理解,内存丢了很多最后当然内存不够用了
标签:垃圾,python,管理机制,回收,对象,gc,内存,引用 来源: https://www.cnblogs.com/totopian/p/15245320.html