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时,列入垃圾回收队列,回收该对象。

引用计数增加的情况:

  1. 一个对象被分配
  2. 将其放入容器中

引用计数减少的器情况:

  1. 使用del语句显示销毁
  2. 对象所在容器被销毁或者容器中删除对象
  3. 引用超出作用域或被重新赋值

    当a作为参数传递给getrefcount时,会产生一个临时引用,以下结果比真实情况 + 1

  1. import sys
  2. a = [1, 2]
  3. print("获取对象a的引用次数:", sys.getrefcount(a)) # 2
  4. b = a
  5. print(sys.getrefcount(a)) # 3
  6. del b
  7. print(sys.getrefcount(a)) # 2
  8. c = list()
  9. c.append(a)
  10. print(sys.getrefcount(a)) # 3
  11. del c
  12. print(sys.getrefcount(a)) # 2
  13. a = [3, 4]
  14. 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扫描也会发生,即为全代扫描。

  1. import gc
  2. gc.get_threshold() ## 分代回收机制的参数阈值设置
  3. (700, 10, 10)
  • 700=新分配的对象数量-释放的对象数量,第0代gc扫描被触发
  • 第一个10:第0代gc扫描发生10次,则第1代的gc扫描被触发
  • 第二个10:第1代的gc扫描发生10次,则第2代的gc扫描被触发