如何判断对象是否死亡

  1. 引用计数算法:在对象中添加一个引用计数器,每当一个地方引用它时,计数器加一,引用失效时,计数器减一。任何时刻计数器为零的对象就是不可能再次被使用的。
    该算法实现简单,效率也很高,但是Java主流虚拟机没有选用引用计数法来管理内存,主要原因是很难解决对象之间相互循环引用的问题。 例子如下:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

  2. 可达性分析算法:当前主流商业程序语言的内存管理子系统都是使用可达性(Reachability Analysis)分析来判断对象是否存活。
    该算法思路为通过一系列称为"GC Roots"的根对象作为起始节点,从这些节点根据引用关系向下搜索,搜索过程所走过的路径称为”引用链(Reference Chain)”,若某个对象到GCRoots不存在任何引用链相连,即对象到GCRoots不可达,则证明此对象是不可能再被使用的。
    image.png
    在Java中,固定作为GC Roots的对象包括:

    • 在虚拟机栈(栈帧中的本地变量表) 中引用的对象,
    • 本地方法栈中引用的对象。
    • 在方法区中类静态属性引用的对象,譬如 Java 类的引用类型静态变量。
    • 在方法区中常量引用的对象, 譬如字符串常量池(String Table)里的引用。

引用分类:

JDK1.2之后,Java对引用的概念进行了扩充

  • 强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即 类似“ Object obj = new Object()”这种引用关系。 无论任何情况下,只要强引用关 系还存在,垃圾收集器就永远不会回收掉被引用的对象。
  • 软引用是用来描述一些还有用,但非必须的对象。 只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。 在 JDK 1.2 版之后提供了 SoftReference 类来实现软引用。
  • 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。 当垃圾收集器开始工作,无论 当前内存是否足够,都会回收掉只被弱引用关联的对象。 在 JDK 1.2 版之后提供了 WeakReference 类来实现弱引用。
  • 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。 一个对象 是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得 一个对象实例。 为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收 集器回收时收到一个系统通知。 在 JDK 1.2 版之后提供了 PhantomReference 类来实 现虚引用。

如何判断一个常量是废弃常量

看有无对象引用该常量。

回收方法区

方法区垃圾回收性价比则较低,因为其回收的条件较于苛刻,方法区回收成果往往远低于此。

  1. 对于方法区中的常量,判断和堆类似,看有无对象引用该常量。
  2. 而对于类是否废弃则较为苛刻,需同时满足下面的3个条件才能算是无用的类:
    • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子类的实例。
    • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、 JSP 的重加载等,否则通常是很难达成的。
    • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

在大量使用反射、动态代理、 CGLib 等字节码框架,动态生成 JSP 以及 OSGi 这类频 繁自定义类加载器的场景中,通常都需要 Java 虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

垃圾回收算法

  1. 标记-清除算法


    最基础的收集算法,根据标记来判断对象是否回收,或根据标记来判断对象是否存活。
    该算法的缺点为:
    1. 执行效率不稳定,当遇到需要回收大量对象时,需要执行大量标记和清除操作者,执行效率随标记对象的数量增长而降低。
    2. 标记、清除之后可能产生大量不连续的内存碎片,空间碎片太多可能导致之后的程序在运行过程中需要分配较大对象时,无法找到足够的连续的空间而不得不触发另一次垃圾收集动作。

image.png

  1. 标记-复制算法


    按照容量将内存分为大小相等的两块,每次只使用其中的一块。当一块用完了就将存活的对象复制到另一块,再将已使用的区域清理掉。
    缺点:标记-复制法在对象存活率较高的时候,效率将降低。将可用内存缩小为了原来的一半,空间浪费过多
    image.png
    额外了解:在 1989 年, Andrew Appel 针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“ Appel 式回收” 。
  2. 标记整理法


    “标记-整理”( Mark-Compact)算法,其中的标记过程仍然与“标记 - 清除”算法一样,但后 续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存。这是一种移动式回收算法。
    缺点:如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域, 移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作 必须全程暂停用户应用程序才能进行。

image.png

  1. 分代收集算法


    将java堆分为新生代和老年代,这样就可以根据各个年代的特点选择合适的垃圾收集算法。
    在新生代中,每次收集都会有大量对象死去,可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
    在老年代中,对象存活的几率较高,而且没有额外的空间进行担保,所以可以选择标记-清除或标记-整理算法进行垃圾收集。