0%

Java中的四种引用

Java中有四种引用,强引用、软引用、弱引用、虚引用。

强引用

强引用不会被回收

1
2
Object obj = new Object();
String str = new String("str");

软引用(SoftReference)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String str1 = new String("softReferenceTest1");
String str2 = new String("softReferenceTest2");
SoftReference<String> softReference1 = new SoftReference<>(str1);
SoftReference<String> softReference2 = new SoftReference<>(str2);

System.out.println(softReference1.get());
System.out.println(softReference2.get());
//解除强引用,让对象可被回收
str2 = null;
System.gc();
System.out.println("===gc===");

System.out.println(softReference1.get());
System.out.println(softReference2.get());
1
2
3
4
5
softReferenceTest1
softReferenceTest2
===gc===
softReferenceTest1
softReferenceTest2

可以看到str2并没有被回收。

当内存充足的时候,软引用对象不会被JVM回收,而当内存不足时,软引用对象会被回收掉。

Android的SoftReferences代码注释里写了,要避免使用SoftReferences进行缓存

在实践中,软引用在缓存场景中效率低下。运行时(Runtime)缺乏足够的信息来决定应该清除哪些引用、保留哪些引用。最致命的是,当面临“清除软引用”和“扩展堆内存”之间的选择时,运行时无法做出合理决策。

由于缺乏对每个引用在应用中的价值评估,软引用的实用性大打折扣:

  • 引用过早被清除会导致重复计算(不必要的性能开销)。

  • 引用过晚被清除则会浪费内存(可能导致内存压力或OOM崩溃)。

推荐替代方案:LruCache

弱引用(WeakReference)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String str1 = new String("weakReferenceTest1");
String str2 = new String("weakReferenceTest2");
WeakReference<String> weakReference1 = new WeakReference<>(str1);
WeakReference<String> weakReference2 = new WeakReference<>(str2);

System.out.println(weakReference1.get());
System.out.println(weakReference2.get());
//解除强引用,让对象可被回收
str2 = null;
System.gc();
System.out.println("===gc===");

System.out.println(weakReference1.get());
System.out.println(weakReference2.get());
1
2
3
4
5
weakReferenceTest1
weakReferenceTest2
===gc===
weakReferenceTest1
null

str1没有被回收,str2被回收了。当没有强引用指向weakReference,一gc就会被回收。Android中经常使用WeakReference来规避内存泄漏的风险。

虚引用(PhantomReference)

1
2
3
4
ReferenceQueue<String> queue = new ReferenceQueue<>();
String str = new String("phantomReferenceTest");
PhantomReference<String> phantomReference = new PhantomReference<>(str, queue);
System.out.println(phantomReference.get());
1
null

phantomReference.get()永远返回null。

虚引用很少使用,且虚引用只有一个构造方法,必须配合ReferenceQueue一起使用。

配合ReferenceQueue使用

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
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();

String str = new String("weakReference+referenceQueue");
WeakReference<String> weakReference = new WeakReference<>(str, referenceQueue);
System.out.println("weakReference=" + weakReference);
System.out.println(weakReference.get());
str = null;
System.gc();
System.out.println("===gc===");

try {
Thread.sleep(100);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println(weakReference.get());

System.gc();

Reference<? extends String> ref = referenceQueue.poll();

if (ref != null) {
System.out.println("========");
System.out.println(ref);
System.out.println(ref.get());
}
1
2
3
4
5
6
7
weakReference=java.lang.ref.WeakReference@566776ad
weakReference+referenceQueue
===gc===
null
========
java.lang.ref.WeakReference@566776ad
null

可以看到当gc之后weakReference.get()已经为null,但是在referenceQueue.poll()却有值,并且为WeakReference@566776ad,和weakReference相等。也就是 gc之后会将weakReference添加进WeakReference关联的ReferenceQueue。 可以用来监听某个对象是否被回收,android中的LeakCanary框架就是利用这个原理进行分析是否有内存泄漏。