Java 的四种引用

在Java中除了基础的数据类型以外,其它的都为引用类型。从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。 (虚引用又称为幻影引用)

一般情况下平时开发中基本上只用到强引用类型,而其他的引用类型较少用,但是它们依旧很重要,作为 Javaer 必须掌握清楚。

强引用

我们平日里面的用到的new了一个对象就是强引用,例如:

1
Object obj = new Object();

上述Object这类对象就具有强引用,属于不可回收的资源,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠回收具有强引用的对象,来解决内存不足的问题。

值得注意的是:如果想中断或者回收强引用对象,可以显式地将引用赋值为null,这样的话 JVM 就会在合适的时间,进行垃圾回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 测试强引用
*
* 测试时,注意设置JVM参数:-Xms128m -Xmx128m -XX:+PrintGCDetails
*
* 因为不断的创建 MyReference 对象,而这些对象是强引用,一直在被 list 维护着,
* 所以在程序运行期间,就算发生垃圾回收,也不会将list维护的强引用对象回收,
* 因此堆内存会不断被占用,直到内存溢出。
*
* @throws Exception
*/
private static void testStrongReferences() throws Exception {
List<MyReference> list = new ArrayList<>();

int index = 1;

while (true) {
int currentIndex = index++;
list.add(new MyReference(currentIndex));
System.out.println("the index = " + index + " MyReference inserted to list");
TimeUnit.MILLISECONDS.sleep(500);
}
}

private static class MyReference {

private final int index;

private int[] data = new int[1024 * 1024];

private MyReference(int index) {
this.index = index;
}

/**
* Java回收该类的一个对象时,就会调用这个已经被重写的finalize()方法。
* 标记当前对象在下一次GC的时候会被垃圾回收,如果不想被回收,那么可以重写这个方法,拯救这个对象和 root 强关联。
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
System.out.println("index = [ "+ index +"] will be GC");
}
}

软引用(SoftReference)

如果一个对象只具有软引用,那么它的性质属于可有可无的那种。如果此时内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

1
2
3
4
5
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
SoftReference reference = new SoftReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;

当内存不足时,软引用对象被回收时,reference.get()为null,此时软引用对象的作用已经发挥完毕,这时将其添加进ReferenceQueue 队列中

如果要判断哪些软引用对象已经被清理:

1
2
3
4
SoftReference ref = null;
while ((ref = (SoftReference) queue.poll()) != null) {
//清除软引用对象
}

单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 软引用测试
*
* 测试时,注意设置JVM参数:-Xms128m -Xmx128m -XX:+PrintGCDetails
*
* 当内存快要耗尽时,GC 会回收掉软引用。
*
* 注意:由于内存占用太快,导致当内存快要耗尽时,GC还没来得及回收掉软引用时,也会出现OOM异常。
*
* @throws Exception
*/
public static void testSoftReferences() throws Exception {
List<SoftReference<MyReference>> list = new ArrayList<>();

int index = 1;

while (true) {
int currentIndex = index++;
list.add(new SoftReference<>(new MyReference(currentIndex)));
System.out.println("the index = " + index + " MyReference inserted to list");
//TimeUnit.MILLISECONDS.sleep(500);
// 当设置吃内存的速度慢一点的时候,即软引用在GC时来得及回收,那么内存会一直有新的空间可以使用,OOM 的情况能得到缓解
TimeUnit.SECONDS.sleep(1);
}
}

弱引用(WeakReference)

如果一个对象具有弱引用,那其的性质也是可有可无的状态。

而弱引用和软引用的区别在于:弱引用的对象拥有更短的生命周期,只要垃圾回收器扫描到它,不管内存空间充足与否,都会回收它的内存。

同样的弱引用也可以和引用队列一起使用。

1
2
3
4
5
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
WeakReference reference = new WeakReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;

单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 虚引用测试
* 测试时,注意设置JVM参数:-Xms128m -Xmx128m -XX:+PrintGCDetails
*
* 虚引用比软引用更脆弱,只要有GC时,虚引用就会被回收
*
* @throws Exception
*/
public static void testWeakReferences() throws Exception {
List<WeakReference<MyReference>> list = new ArrayList<>();

int index = 1;

while (true) {
int currentIndex = index++;
list.add(new WeakReference<>(new MyReference(currentIndex)));
System.out.println("the index = " + index + " MyReference inserted to list");
TimeUnit.MILLISECONDS.sleep(500);
}
}

虚引用(PhantomReference)

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

注意:虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

1
2
3
4
5
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference reference = new PhantomReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;

单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 幻影引用
* 测试时,注意设置JVM参数:-Xms128m -Xmx128m -XX:+PrintGCDetails
*
* 幻影引用必须配合 ReferenceQueue 使用,
* 因为使用 PhantomReference 对象本身是拿不到该具体实例,当这个幻影引用被回收时,就会放到 ReferenceQueue 队列中
*
* 最佳实践:
* @See org.apache.commons.io.FileCleaningTracker
* org.apache.commons.io.FileCleaningTracker.Tracker
*/
private static void testPhantomReferences() throws Exception {
int index = 10;
MyReference reference = new MyReference(index);
ReferenceQueue referenceQueue = new ReferenceQueue<>();
MyPhantomReference phantomReferences = new MyPhantomReference(reference, referenceQueue, index);

reference = null;
System.gc();

// 等待真正GC一下, 保证虚引用被回收
TimeUnit.SECONDS.sleep(2);
// phantomReferences.get() 拿不到对象
System.out.println(phantomReferences.get());
// 虚引用被回收会进入 ReferenceQueue 队列中
Reference object = referenceQueue.remove();
((MyPhantomReference)object).doAction();
}

private static class MyPhantomReference extends PhantomReference<Object> {

private final int index;

public MyPhantomReference(Object referent, ReferenceQueue<? super Object> queue, int index) {
super(referent, queue);
this.index = index;
}

public void doAction() {
System.out.println("index = [ "+ index +"] will be GC");
}
}

总结

  1. 对于强引用,平时在编写代码时会经常使用。

  2. 而其他三种类型的引用,使用得最多就是软引用和弱引用,这两种既有相似之处又有区别,他们都来描述非必须对象。

  3. 被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。

updated updated 2024-01-01 2024-01-01
本文结束感谢阅读

本文标题:Java 的四种引用

本文作者:woodwhales

原始链接:https://woodwhales.cn/2020/07/05/071/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%