垃圾收集器与内存分配策略

对象的生存与死亡

引用计数与可达性分析算法

  1. 引用计数:当对象被引用时,计数器+1;当引用失效时,计数器-1。效率高,但无法解决循环引用的问题。
  2. 可达性分析:以GC Root为起始点,向下搜索,搜索走过的路径称为引用链,当从GC Root到对象不可达时,该对象为孤立对象,即可回收。

    • GC Root:

      • 虚拟机栈(栈帧中的局部变量表)中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中JNI引用的对象

引用强度

  1. 强引用:程序代码中普遍存在的,如使用new操作符产生的引用。只要强引用还存在,系统将不会回收被强引用的对象。
  2. 软引用:SoftReference类实现,用来描述一些还有用但是并非必需的对象。当系统将要发生内存溢出时,软引用对象将被列入可回收的范围内并进行二次回收,若回收后仍然没有足够的内存,系统才会抛出内存溢出异常。
  3. 弱引用:WeakReference类实现,用来描述非必需对象。无论内存是否足够,弱引用对象都只能生存到发生下一次垃圾收集之前。
  4. 虚引用:PhantomReference类实现,仅用于在对象被回收时得到通知。虚引用完全不影响对象的生存时间,也无法通过虚引用来获取一个对象实例。

对象的“自我拯救”

  1. 覆盖finalize()方法,且尚未被系统调用
  2. 在finalize()方法中与引用链重新建立引用关系
  3. 这种方法只能使用一次,因为finalize()方法只能被系统调用一次,在面临下一次垃圾收集的时候,finalize()方法将不再被调用

方法区的回收

  1. 常量:与堆中的对象的回收类似,当没有字面量引用常量池的常量时,如有必要,常量将会被清理出常量池。
  2. 方法:与常量类似。
  3. 字段:与常量类似。
  4. 类:需要满足“无用的类”的条件,即使满足虚拟机也不一定会回收,可通过参数控制。

    • 无用的类判定条件:

      • 该类的所有实例都已被回收,即堆中不存在该类的任何实例。
      • 加载该类的ClassLoader已被回收。
      • 该类的java.lang.Class方法没有被引用,无法在任何地方通过反射访问该类的方法。

垃圾回收算法

  1. “标记-清除”算法:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。缺点:效率低;产生大量内存碎片。
    “标记-清除”算法示意图
  2. “复制”算法:将可用内存划分为大小相等的两块,每次只使用其中的一块,当进行垃圾回收时,将存活的对象复制到另一块上,再把原来的空间一次清理掉。优点:实现简单,运行高效。缺点:将内存缩小了一半,代价高昂;存活率较高时复制操作增加,效率降低。
    “复制”算法示意图

Hotspot VM对新生代的垃圾回收算法采用的是复制算法,但没有将两块内存划分为大小相等,而是分为Edenh和Survivor,比例为8:1,当Survivor空间不够用时,需要依赖其他内存进行分配担保。

  1. “标记-整理”算法:与“标记-清除”算法一样,都是先标记出所有需要回收的对象,但后续不是直接清理可回收的对象,而是让存活的对象向一端移动,然后清理掉端边界以外的内存。
    “标记-整理”算法示意图
  2. 分代收集算法:针对对象存活周期的不同将内存划分为若干块,对每一块采用合适的算法去进行垃圾回收。如新生代的存活率较低,则采用复制算法;老年代存活率较高,且没有额外内存提供分配担保,必须采用“标记-清除”或“标记-整理”算法。