https://mp.weixin.qq.com/s/PGfpBKyzaRkKPYEI72c-Nw https://www.cnblogs.com/traditional/p/13695846.html https://www.cnblogs.com/traditional/p/13698244.html
内存池
预先在内存中申请一定数量的,大小相等的内存块备用,当有新的内存需求时,就先从内存池中分配内存,不够再申请新的内存,作用是减少内存碎片,提升效率。
python内置的对象(dict\list…)都有独立的私有内存池,对象之间的内存池是不共享的
关于内存释放: 当一个对象的引用计数=0时,python会调用析构函数,并在析构函数中也采用内存池机制,从内存池申请的内存释放时会归还内存池中
垃圾回收机制
python的垃圾回收机制采用引用计数机制为主,标记-清除和分代回收为辅,其中标记-清除机制用来解决计数引用带来的循环引用而无法释放内存的问题,分代回收机制是为了提升垃圾回收的效率。
引用计数
py通过引用计数来记录该对象被其他使用的对象引用的次数,当一个对象的引用计数为0时,列入垃圾回收队列,回收该对象。
引用计数增加的情况:
- 一个对象被分配
- 将其放入容器中
引用计数减少的器情况:
- 使用del语句显示销毁
- 对象所在容器被销毁或者容器中删除对象
- 引用超出作用域或被重新赋值
当a作为参数传递给getrefcount时,会产生一个临时引用,以下结果比真实情况 + 1
import sys
a = [1, 2]
print("获取对象a的引用次数:", sys.getrefcount(a)) # 2
b = a
print(sys.getrefcount(a)) # 3
del b
print(sys.getrefcount(a)) # 2
c = list()
c.append(a)
print(sys.getrefcount(a)) # 3
del c
print(sys.getrefcount(a)) # 2
a = [3, 4]
print(sys.getrefcount(a)) # 2
当引用计数遇到两个对象相互引用的情况,引用计数不会归0,对象也不会被销毁,从而造成内存泄漏,针对该情况,python引入了标记清除
标记-清除
在引用计数的基础上,解决循环引用的问题,即指两个对象相互引用或者自己引用自己
上面描述的垃圾回收的阶段,会暂停整个应用程序,等待标记清除结束后才会恢复应用程序的运行。为了减少应用程序暂停的时间,Python 通过“分代回收”(Generational Collection)以空间换时间的方法提高垃圾回收效率
分代回收
分代回收是基于这样的一个统计事实,对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的 比例通常在 80%~90%之间。因此,简单地认为:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度,是一种以空间换时间的方法策略。
Python将所有的对象分为年轻代(第0代)、中年代(第1代)、老年代(第2代)三代。所有的新建对象默认是 第0代对象。当在第0代的gc扫描中存活下来的对象将被移至第1代,在第1代的gc扫描中存活下来的对象将被移至第2代。
gc扫描次数(第0代>第1代>第2代)
当某一代中被分配的对象与被释放的对象之差达到某一阈值时,就会触发当前一代的gc扫描。当某一代被扫描时,比它年轻的一代也会被扫描,因此,第2代的gc扫描发生时,第0,1代的gc扫描也会发生,即为全代扫描。
import gc
gc.get_threshold() ## 分代回收机制的参数阈值设置
(700, 10, 10)
- 700=新分配的对象数量-释放的对象数量,第0代gc扫描被触发
- 第一个10:第0代gc扫描发生10次,则第1代的gc扫描被触发
- 第二个10:第1代的gc扫描发生10次,则第2代的gc扫描被触发