# 垃圾回收机制

# 内存泄漏

  • 内存泄漏是指程序本身没有设计好,导致程序未能释放已不再使用的内存。注意不是指内存出现了信息安全问题,被恶意程序利用。
  • 内存泄漏指代码在分配了某段内存后,因为设计错误,失去了对这段内存的控制,从而造成了内存的浪费。注意不是指内存在物理上消失了。
  • 引用计数可能会导致循环引用问题,而循环引用会导致内存泄漏。

# 垃圾回收机制

  • python中一切皆对象,所有的变量本质上都是对象的一个指针。

  • Python中的自动化内存管理以引用计数为基础,同时以标记-清除和分代收集为辅。

  • 我们通过为对象设置引用计数(指针数)判断对象被利用(计数为0则需要回收)。

    • 对象被创建、引用、作为参数传出、存入容器时引用计数+1

    • 对象别名被显式销毁、别名被赋予新的对象,对象离开作用域、对象所在容器被销毁或对象被从容器中删除时引用计数-1

    • 可以通过sys​模块的getrefcount​函数来获得对象的引用计数

      • 对象作为函数参数传递时会将引用计数+1避免对象被提前销毁,在返回后再将引用技术-1。因此对象刚创建后getrefcount​得到的结果也是2。
    • 循环引用或者引用环的情况下,虽然引用计数不为0,但是仍然应该被回收,python垃圾会使机制能处理这一情况。可以显式调用 gc.collect() ,来启动垃圾回收。

  • 引用计数是其中最简单的实现,不过这只是充分非必要条件,因为循环引用需要通过不可达判定,来确定是否可以回收,因为循环引用可能导致内存泄漏问题。

  • Python 使用标记清除(mark-sweep) 算法和分代收集(generational),来启用针对循环引用的自动垃圾回收和提高垃圾回收的效率

    • 标记清除算法。对于一个有向图,如果从一个节点出发进行遍历,并标记其经过的所有节点;那么,在遍历结束后,所有没有被标记的节点,我们就称之为不可达节点。显而易见,不可达节点的存在是没有任何意义的,因此我们需要对它们进行垃圾回收。

      为什么标记清除算法能解决循环引用问题:经过一次次垃圾回收,循环引用的环一定是不可达的。

      learn.lianglianglee.com/... (opens new window)

    • 分代收集算法。

      1. 在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过分代回收(空间换时间)的方法提高垃圾回收效率。
      2. 分代收集基于的思想是,新生的对象更有可能被垃圾回收,而存活更久的对象也有更高的概率继续存活。
      3. 分代回收的基本思想是Python 将所有对象分为三代,从第0代到第2代扫描频率依次降低。刚刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值(一定数量)时,即新增对象的数量达到了一定值,就会对这一代对象启动垃圾回收。对象会在年龄达到一定程度后,被放到年龄较大的队列中,并且随着年龄的增大,它们被扫描的频率也会降低。这样,在大多数情况下,垃圾回收器只需要扫描年龄较小的对象,可以大大提高回收的效率。