软引用、弱引用和虚引用

强引用

强引用(strong reference)是使用最普遍的引用,如果一个对象具有强引用,那垃圾回收器绝不会回收它,例如Object obj = new Object();,即使当内存空间不足时,JVM宁愿抛出OOM,也不会回收具有强引用的对象来解决内存不足的问题。

我们可以显示的设置对象为null,或者跳出对象的生命周期范围,让垃圾收集器将其判定为不存在引用,是个可以被回收的对象,例如obj = null;

全局对象:手动的将对象赋值为null,最典型的全局对象设置为null的就是在ArrayList的clear()方法中,对于类中的全局变量elementData来说,仅仅的将其置为null是不可行的,因为数组中的对象还是会继续引用的,所以此时需要将数组中每个位置的对象全部置为null,也就是将所有的对象的引用都释放掉,这样才会被GC回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;

// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}

局部变量:一般情况下,一个对象保存在堆中,对象的引用保存在Java栈中,由于栈是线程私有的,所以当线程结束时,Java栈被自动回收,这时堆中对象的引用数-1,直到引用数变为0后,可以被GC回收。例如:

1
2
3
public void method() {
Object obj = new Object();
}

我们通过new创建的对象一般都是强引用

弱引用

弱引用(weak reference)是可以被GC强制回收的,当垃圾收集器发现一个存活的弱可达对象时,就会将其放入响应的ReferenceQueue中,之后可能会遍历这个ReferenceQueue并执行响应的清理,弱可达对象是指该对象的引用只剩下弱引用。

我们可以通过弱引用的get()方法给对象赋值给新的强引用,在回收前,GC会再次判断该对象是否可以安全回收。所以,弱引用的对象的回收过程可以横跨多个GC周期,在Java中可以使用WeakReference类创建一个弱引用对象。例如:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception {
String cc = new String("串串");
// 创建对象cc的弱引用
WeakReference<String> cc_wr = new WeakReference<>(cc);

// ① cc = null;
// ② System.gc();
// 为对象cc创建一个强引用,若对象cc已经被回收,则返回null
String cc_s = cc_wr.get();
// ③ cc = null;
System.out.println(cc_s);
}

分析

  1. 仅开启①

    image-20200530000738739

    弱引用对象尚未被回收

  2. 仅开启②

    image-20200530001335758

  3. 仅开启③

    image-20200530001504957

  4. 开启①②

    image-20200530001545556

    哇啊哦~对象被回收了,这是因为我们先将对象的弱引用断开,然后又手动进行了一次gc,把对象给回收了

  5. 开启②③

    image-20200530002013851

  6. 开启①②③

    image-20200530002029511

软引用

软引用(Soft Reference)是比弱引用更难被垃圾回收器回收的对象,什么时候回收软引用完全由JVM自己决定,一般只会在即将OOM时才会回收软引用,算是JVM内存管理最后的倔强。这就意味着可能会有非常频繁的Full GC,STW时间也变长,因为老年代中的存活对象多了。

1
2
3
4
public static void main(String[] args) throws Exception {
SoftReference<String> cc_sr = new SoftReference<>(new String("串串"));
System.out.println(cc_sr.get());
}

虚引用

虚引用和弱引用、软引用不同,它并不影响对象的生命周期,使用java.lang.ref.PhantomReference类表示,和弱引用、软引用不同的是,虚引用必须和引用队列关联使用,当GC准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用放进与之关联的引用队列中。

为了防止可回收对象的残留,虚引用对象不应该被获取,PhantomReference的get()方法始终返回null,虚引用不会被GC自动清除,因为它们被存放到队列中,通过虚引用可达的对象会继续留在内存中,直到调用此引用的clear()方法,或者引用自身变为不可达。

也就是说我们如果不手动调用clear()方法来清除虚引用,则非常可能造成OOM而导致JVM宕机。

1
2
3
4
5
public static void main(String[] args) throws Exception {
ReferenceQueue<String> rq = new ReferenceQueue<>();
PhantomReference<String> cc_p = new PhantomReference<>(new String("cc"), rq);
System.out.println(cc_p.get());
}

可以使用JVM参数-XX:+PrintReferenceGC查看各类引用对GC的影响