0%

常用垃圾回收算法

清明节,又称踏青节、行清节、三月节、祭祖节等,节期在仲春与暮春之交。清明节源自上古时代的祖先信仰与春祭礼俗,兼具自然与人文两大内涵,既是自然节气点(二十四节气之一),也是传统节日。清明节与春节、端午节、中秋节并称为中国四大传统节日。

一、概念

      在计算机科学中,垃圾回收(Garbage Collection,缩写为GC)是指一种自动的存储器管理机制,当某个程序占用的一部分内存空间不再被这个程序访问时,这个程序会借助垃圾回收算法向操作系统归还这部分内存空间。垃圾回收器可以减轻程序员的负担,也减少程序中的错误。垃圾回收最早起源于LISP语言,当前许多语言如Smalltalk、Java、PHP、Go、C#等都支持垃圾回收器。

二、分类

  1. 引用计数法(最早也是最简单的垃圾回收实现方法,1960年由George E. Collins提出)

    • 原理:为对象附加一个计数器,当有其他对象引用这个对象时计数器加一,反之引用解除时减一。这种算法会定期检查尚未被回收的对象的计数器,为零的话则回收其所占物理空间,因为此时的对象已经无法访问。
    • 优点:
      • 实时性较高,无需等到内存不够的时候才开始回收,运行时根据对象的计数器是否为0就可以直接回收。
      • 在垃圾回收过程中应用无需挂起。如果申请内存时,内存不足则立刻报out of memory错误。
      • 区域性(回收过程影响范围),更新对象的计数器时只是影响到该对象,不会扫描全部对象。
    • 缺点:
      • 每次对象被引用时,都需要去更新计数器,有一点时间开销。
      • 浪费CPU资源,即使内存够用任然在运行时进行计数器的统计。
      • 无法解决循环引用问题(最大的缺点,已有解决方案php5.3.0即参考此文献解决了循环引用的问题)。
  2. 标记-清除算法

    • 原理:暂停整个程序的全部运行线程,让回收线程以单线程进行扫描标记,将垃圾回收分为2个阶段————标记和清除。标记阶段从根节点开始标记引用的对象;清除阶段则是直接清理未被标记引用的对象,回收完成后恢复运行线程。
    • 优点:
      • 解决了引用计数算法中的循环引用的问题
    • 缺点:
      • 效率低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。
      • 碎片化严重,从而使大容量对象不容易获得连续的内存空间,造成空间浪费。
  3. 标记-压缩算法(优化了的标记——清除算法),也称为标记-整理算法

    • 原理:标记阶段从根节点开始,对对象的引用进行标记;清理阶段并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。
    • 优点:
      • 解决了标记清除算法的碎片化的问题。
    • 缺点:
      • 标记压缩算法多了一步————对象移动内存位置,其效率也有一定的影响。
  4. 标记-复制算法

    • 原理:将所拥有的内存空间分成两个部分。程序运行所需的存储对象先存储在其中一个分区(定义为“分区0”)。回收过程仍然是暂停整个程序的全部运行线程,进行标记后将保留的存储对象搬运汇集到另一个分区(定义为“分区1”)完成回收,程序在本次回收后将接下来产生的存储对象会存储到“分区1”。在下一次回收时,两个分区的角色对调。(简单理解:将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收)
    • 优点:
      • 如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高
    • 缺点:
      • 同优点相反
  5. 增量回收器算法(增量GC名称的由来跟全量GC相对,就是每次只处理一小部分的对象)

    • 原理:将所有的内存空间分成若干分区,程序运行所需的存储对象会分布在这些分区中,每次只对其中一个分区进行回收操作,从而避免暂停所有正在运行的线程来进行回收,允许部分线程在不影响回收行为下保持运行,并且降低回收时间,增加程序响应速度。
    • 优点:
      • 不会造成stop the world
    • 缺点:
      • 实现过程复杂
  6. 分代算法

    • 原理:由于复制算法对于存活时间长、大容量的储存对象需要耗费更多的移动时间,和存在储存对象的存活时间的差异。需要程序将所拥有的内存空间分成若干分区,并标记为年轻代空间和年老代空间。程序运行所需的存储对象会先存放在年轻代分区,年轻代分区会较为频密进行较为激进垃圾回收行为,每次回收完成幸存的存储对象内的寿命计数器加一。当年轻代分区存储对象的寿命计数器达到一定阈值或存储对象的占用空间超过一定阈值时,则被移动到年老代空间,年老代空间会较少运行垃圾回收行为。一般情况下,还有永久代的空间,用于涉及程序整个运行生命周期的对象存储,例如运行代码、数据常量等,该空间通常不进行垃圾回收的操作。通过分代,存活在局限域、小容量、寿命短的存储对象会被快速回收;存活在全局域、大容量、寿命长的存储对象就较少被回收行为处理干扰。

如何判断一个对象是否应该回收

  • 引用计数,该对象每被一个地方引用,计数器就加+1,引用失效时,计数器-1,计数器为0时的对象就不能再被使用,很难解决循环引用的问题。
  • 可达性分析法,通过gc roots作为起点,从这些节点开始向下搜索,当一个对象到gc roots没有任何引用链十则代表这个对象就是不可用的。

什么可以作为GC ROOT呢?

  • 类静态属性中引用的对象(方法区)
  • 常量引用的的对象(方法区)
  • 虚拟机栈中引用的对象(栈帧中的本地变量表)
  • jni引用的对象(本地方法栈)
  • 除了垃圾回收,还有哪些工作会造成cpu负载过高,100%负载,并给出排查过程

三、参考

  1. 维基百科
  2. 知乎专栏
  3. segmentfault
  4. 腾讯云