第 4 章 垃圾回收概念与算法

一、认识垃圾回收

  垃圾回收是Java体系重要的组成部分,与C/C++不同,Java提供了自动化的垃圾回收机制。
  垃圾回收简称GC(Garbage Collection),GC中的垃圾特指存在于内存中的,不会再被使用的对象。回收就是将这部分数据清理掉,这样就会有空闲的区域被腾出来。

二、常用的垃圾回收算法

  垃圾回收算法是垃圾回收的理论基础,包括:引用计数法,标记压缩法,标记清除法,复制算法以及分代、分区等思想 。

引用计数法 (Reference Counting)

  这是一种最经典也最古老的垃圾收集方法,其原理也比较简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,引用失效时则减1。直到A的引用计数器为0时,则A就不可能再被使用。
  但是,引用计数法有两个非常严重的问题:
  (1)引用计数法无法处理循环引用的情况。假设有对象A、B,且只有A、B间有相互引用,这种情况下,垃圾收集器是无法识别的,将会引起内存泄漏。因此,在Java中,没有使用这种算法。
  (2)引用计数器要求在每次引用产生和消除时,要伴随一个加法和减法操作,这对性能会有一定影响。

标记清除法 (Mark-Sweep)

  标记清除算法是现代垃圾回收算法的思想基础。标记清除算法将垃圾回收分为标记和清除两个阶段。首行,标记所有从根节点开始的可达对象,而未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
  标记清除算法可能产生的最大问题是空间碎片。且在java虚拟机中,不连续的内存空间的工作效率要低于连续的空间。

复制算法 (Copying)

  复制算法的核心思想是:将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,再交换两块内存的角色。
  若系统中垃圾比例很高,需要复制的存活对象数量就会相对较少,这种场景下,复制算法效率很高,且可确保回收后的内存空间是没有碎片的。但复制算法的代价却是将系统内存折半,因此,单纯的复制算法也很难让人接受。
  在java的新生代串行垃圾回收器中,就使用了复制算法的思想,新生代分为eden空间、from空间和 to空间 3部分。其中from 和 to空间便可以视为用于复制的两块大小相同,地位相等,可进行角色互换的空间块。from 和 to空间也称为 survivor空间,即幸存空间,用于存放未被回收的对象。
  名词解释:
  新生代:存放年轻对象的堆空间。年轻对象指刚刚创建的,或者经历过垃圾回收次数不多的对象。
  老年代:存放老年对象的堆空间。老年对象指经历过多次垃圾回收依然存活的对象。
  在垃圾回收阶段,eden中的存活对象会向未使用的survivor复制(假设是to),survivor-from中的年轻对象也会向survivor-to中复制。其中大对象,或者老年对象会直接进入老年代,且如果 to空间已满,对象也会直接进入老年找。这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间浪费。如下图

这里写图片描述

  复制算法比较适合用于新生代。因为在新生代,对象朝生夕灭,大约90%的新建对象会被很快回收。进而,垃圾对象通常会多于存活对象,复制算法的效果会比较好。

标记压缩法(Mark-Compact)

  标记压缩算法是一种老年代回收算法,是对标记清除算法的优化。首先,同样对所有可达对象做一次标记,但之后会将所有存活对象压缩到内存的一端,接着,清理边界外的所有空间。如此,避免了碎片的产生,又不需要两块相同的内存空间。因此,也被称为标记压缩清除算法。

分代算法(Generational Collecting)

  前面提到的复制,标记清除,标记压缩算法中,并没有一种算法可以完全替代其他算法。因此有了分代算法,它将内存根据对象的特点分成几块,根据每块的特点,使用不同的回收算法。如,新生代使用复制算法,老年代使用标记清除/标记压缩算法。

这里写图片描述

  对新生代和老年代来说,新生代回收频率很高,每次耗时很短,老年代回收频率较低,但会耗费更多时间。为了支持高频率的新生代回收,虚拟机使用一种叫卡表的数据结构。卡表为一个比特位集合,每一个比特位表示老年代的某一个区域所有对象是否持有新生代对象的引用 。只有当卡表位为1时,才需要扫描给定区域的老年代对象,而卡表位为0的,老年代对象一定不含有新生代对象的引用。

分区算法(Region)

  分代算法是按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间,每一个小区间独立使用,独立回收。这样做的好处是可以根据设定的可容忍停顿时间,每次合理的回收若干的小区间。

三、发现垃圾:判断可触及性

  垃圾回收的基本思想是考察每一个对象的可触及性,即从根节点开始是否可以访问到这个对象,但有时,一个无法触及的对象有可能在某一个条件下“复活”自己,如果这样,对它的回收就是不合理的,为此,有了以下对象可触及性状态的定义,并规定在什么状态下,才可安全的回收对象。
  可触及的:从根节点开始,可以到达的对象
  可复活的:对象的所有引用都被释放,但有可能在finalize()函数中复活的。
  不可触及的:对象的finalize()函数被调用,且没有复活的,不可触及的对象不可能被复活,因为finalize()函数只会被调用一次。

对象的复活

public class CanReliveObj {
    public static CanReliveObj obj;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        obj = this; // 将当前实例赋值给全局变量obj,obj成为可触及对象
    }

    public static void main(String[] args) {
        obj = new CanReliveObj();
        obj = null;
        System.gc(); // 对象清理前会调用finalize,导致obj复活
        obj = null;  // 再次释放obj
        System.gc(); // finalize只会在前次 gc时被调用一次,本次Gc,obj不再复活
    }
}

  即,引用 如果外泄,对象就会复活
  注意:finalize()函数是一个非常糟糕的模式,强烈不推荐使用finalize()函数释放资源。
  第一,finalize()有可能发生引用外泄,而无意中复活对象
  第二,finalize()是由系统调用的,调用时间是不明确的,因此,不是一个好的资源释放方案,推荐 try-catch-finally 中进行资源释放。

引用和触及性的强度

  Java中提供了4个级别的引用强度:强引用、软引用、弱引用和虚引用,强引用的对象是可触及的。相对的软引用,弱引用和虚引用的对象是软可触及,弱可触及和虚可触及的,一定条件下均可被回收。

强引用 —- 不可被回收的引用

  强引用就是程序中一般使用的引用类型,是可触及的,不会被回收。
  强引用具备以下特点:
  强引用可以直接访问目标对象
  强引用的对象在任何时候都不会被回收,jvm宁愿抛出OOM异常,也不会回收强引用对象
  强引用可能导致内存泄漏

软引用 —- 可被回收的引用

  程序中,通过以下方式创建软引用

User user = new User();
SoftReference<User> userSoftRef = new SoftReference<User>(user);    // 创建软引用
user = null;    // 去除强引用
System.out.println(userSoftRef.get());

  GC未必会回收软引用,但当内存资源紧张时,软引用会被回收,所以软引用对象不会引起内存溢出。
  类似的,通过 new WeakReference<T>(T t) 和 new PhantomReference<T>(T t)来创建弱引用和虚引用。

弱引用 —- 发现即回收

  弱引用是一种比软引用要弱的引用类型,在系统GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象回收。
  弱引用和软引用一样,都可以在构造引用时指定一个引用队列,当引用对象被回收时,就会加入指定的引用队列,通过这个队列可以跟踪对象的回收情况。
  软引用,弱引用都非常适合来存储那些可有可无的缓存数据。当系统内存不足时,这些缓存数据会被回收,从而不会导致内存溢出。

虚引用 —- 对象回收跟踪

  虚引用是所有引用类型中最弱的一个。虚引用和没有引用几乎是一样的,随时都可能被回收。当试图通过get()方法取得强引用时,总是会失败,并且虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
  当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
  由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。

四、垃圾回收时的停顿现象:Stop-The-World(STW)

  垃圾回收的任务是识别和回收垃圾对象以进行内存清理,大部分情况下,会要求系统进入一个停顿的状态,这样才不会有新的垃圾产生。保证系统状态在某一个瞬间的一致性。停顿产生时,整个应用程序会被卡死,没有任何响应,也叫做“Stop-The-World”(STW)。

五、总结一下

这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/xuguangyuansh/article/details/78603108