垃圾收集概述

  1. 1. 什么是垃圾?

运行的程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。

  1. 1. 为什么需要进行垃圾回收?

如果没有GC,那么内存迟早会被耗完。除了释放没有用的对象,垃圾收集也可以清楚内存中记录的碎片。

  1. 1. 垃圾回收的地点?
  • • 垃圾收集可以对年轻代回收,也可以对老年代进行回收,甚至可以全堆和方法区的回收;
  • • 启动堆是垃圾收集器的工作重点;
  • • 从次数.上来讲
  • • 频繁收集Young区
  • • 较少收集Old区
  • • 基本不动元空间

system.gc()

  • • system.gc()或者Runtime.getRuntime().gc()的调用,会显示的触发FulIGC,同 时对老年代和新生代进行回收,尝试释放被丢弃的对象占用的内存。
  • • System.gc()调用附带一个免责声明,无法保证对垃圾回收器的调用

内存溢出

  • • javadoc对OOM的解释就是:没有空闲的内存,并且垃圾收集器也没法提供更多的内存
  • • JVM内存不够的两个原因
  • • java堆内存设置不够
  • • 代码中创建了大量的大对象,而且长时间不能被垃圾回收器收集(存在被引用)
  • • 在抛出OOM之前,通常垃圾回收器会被触发,尽可能的去清理出空间(例如引用分析中的涉及jvm尝试会收的软引用指向的对象)

内存泄漏

  • • 严格来说,只有对象不会再被程序用到,但是GC又不能回收他们的情况,才叫内存泄露
  • • 内存泄露不会立即引起程序奔溃,但是会一点一点的蚕食,直至内存被耗尽,直至出现OOM异常 例如:


  • • 举例
  • • 单例模式 -------- 单例模式的生命周期和应用程序一样长, 如果持有对外部对象的引用的话,那么这个外部对象是不可回收的,会导致内存泄露的问题
  • • 一些close的资源未关闭 --------- 数据库连接,网络连接和io连接必须手动的close,否则不能被回收。

SWT(stop the word)

  • • Stop The Word --------- 指的是发生GC的时候,会产生程序的停顿。停顿时整个应用程序都会被暂停,没有任何响应,就和卡死了一样;
  • • 可达性分析算法中枚举根节点(GC Roots)会导致所有的Java执行的线程停顿
  • • 分析工作必须保证在一个能确保一致性的快照中进行
  • • 一致性指的是整个分析期间执行系统看起来像被冻结在某个时间点上面
  • • 如果出现分析过程中对象引用关系的不断变化则分析结果的准确性无法被确保
  • • SWT与哪款GC无关,所有GC都会有这个事件
  • • STW是JVM在后台自动完成的,,在用户不可见的情况下,将用户的所有线程停止
  • • 开发过程中不要使用System.gc() ,会导致SWT的产生

并行与并发

并行:同时执行

并发:增抢线程前后执行

安全点

  • • 程序并非所有地方都能停下来进行GC,只有在特定位置才能停下来GC,这些位置就称之为安全点
  • • 安全点选择很重要,如果太少会导致GC等待时间太长,如果太频繁会导致性能问题
  • • 会选择执行时间长的指令作为安全点,如方法调用,循环跳转和异常跳转等
  • • 如何判断发生GC时,线程都跑到最近的安全点停顿下来呢
  • • 抢先式中断(目前不采用了)
  • • 主动式中断

安全区域

指一段代码中,对象的引用关系不会发生变化,这个位置中任何位置开始GC都是安全的

引用

我们希望这样一类对象,当内存空间足够时,如果内存空间进行了垃圾回收依旧还是很紧张,则可以抛弃这些对象 ------ 很多系统的缓存功能都符合这样的应 用场景。

对应的对象:

JVM垃圾收集器(一) ------ 垃圾收集概述_虚引用


强引用(Strang Reference)

  • • 传统的引用方式,无论任何情况下,只要引用关系还存在,垃圾回收就不会回收掉被引用的对象(指的是可达性分析中被标记为不是垃圾的对象)
  • • java中最常见的引用类型就是强引用(99%以上都是), 也是默认的引用类型。
  • • 强引用对象是可触及的,垃圾回收器永远不会回收被引用的对象
  • • 强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿报OOM,也不会回收强引用指向的对象。

例如:

StringBuffer str = new StringBuffer("Hello Word");

局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是Str ingBuffer实例的强引用。

对应的内存结构:


软引用(Soft Reference)

  • • 当系统将要发生内存溢出之前,将这些对象列入回收对象进行二次回收,回收后还没有足够的内存,才抛出OOM异常(指的是可达性分析中被标记为不是垃圾的对象)
  • • 内存不足及回收
  • • 软引用通常用来实现内存敏感的缓存,比如高速缓存就用到了软引用。如果还有空闲的内存,就保留缓存,当内存不足就清理掉。

JDK 1.2版之后提供了java.lang.ref.SoftReference类来实现软引用。

例如:

Object obj = new Object();//声明为强引用
SoftReference<Object> softReference = new SoftReference<>(obj);
obj = null;//摧毁强引用

弱引用(Weak Reference)

  • • 被弱引用引用的对象只存活到下一次垃圾街回收之前, 当垃圾收集器工作时,无论空间是否足够,都会回收掉被引用的对象
  • • 发现即回收
  • • 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。在系统GC时,只要发现弱引用,不管系统空间是否充足,都会回收掉被弱引用关联的对象
  • • 软引用和弱引用都适合来保存一些可有可无的缓存
  • • 弱引用对象与软引用对象的最大不同就在于,当GC在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC总是进行回收。弱引用对象更容易、更快被GC回收。

JDK 1. 2版之后提供了java.lang.ref.WeakReference类来实现弱引用。

例如:

Object obj = new Object();
WeakReference<Object> softReference = new WeakReference<>(obj);
obj = null;

面试题:你开发中使用过WeakHashMap吗?

虚引用(Phantom Reference)

  • • 一个对象是否有虚引用的存在,完全不会对其生存时间造成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置 虚引用关联的唯一目的就是能够在这个对象被收集器回收时收到一个系统通知。
  • • 对象回收跟踪
  • • 虚引用必须和引用队列一起使用。虚引用在创建时必需提供一个引用队列作为参数, 当垃圾回收器准备回收一个对象时, 发现他还有虚引用,就会在回收对象后,将虚拟引用加入引用队列,已通知应用程序的回收情况。
  • • 由于虚引用可以跟踪对象的回收时间,所以也可以将资源释放的一些操作放置在虚引用中执行和记录

在JDK 1. 2版之后提供了PhantomRe ference类来实现虚引用。

例如:

Object obj = new Object();
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, referenceQueue);
obj = null;

终结引用(Final Reference)

  • • 他用以现实对象的finalize()方法,也可以称之为终结引用
  • • 无需手动编码,配合引用队列使用
  • • 在GC时,终结器引用队列

finalize

finalize()是Object类的一个方法,一个对象重写该方法将会在垃圾回收之前执行,finalize()方法是对象逃脱死亡命运的最后一次机会,但是,该方法只会执行一次。

可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:

  • • 对象在进行可达性分析后发现没 有与GC Roots相连接的引用链,那它将会被第一次标记
  • • 通过第一次标记之后,会再进行一次筛选,筛选的条件是此对象是 否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用 过,那么虚拟机将这两种情况都视为“没有必要执行”。

代码示例对象一次自我拯救的演示:

public class FinalizeEscapeGC {

    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes, i am still alive :)");
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!"); FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        //对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
        // 下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
    }
}

执行结果:

finalize method executed!
yes, i am still alive :)
no, i am dead :(

从上面的代码中看见,System.gc();触发了垃圾回收器,并在第一次回收过程中调用了finalize()方法,而且在第一次中成功的逃脱了,但是在第二次执行System.gc();触发垃圾回收器后并没有执行finalize()方法,并自救失败。很好的说明了finalize()方法只会执行一次。

注意:并不建议重写finalize()来进行垃圾回收之前的处理。它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序,如今已被官方明确声明为不推荐使用的语法。