Java引用类型简介与应用

Java引用类型

Java中有四种引用类型。其中SoftReference、WeakReference、PhantomReference构造时都可以指定ReferenceQueue,当目标对象 (Referent) 被回收时,Reference会被添加到队列中。

中文

英文

取得目标对象 (Referent) 方式

垃圾回收条件

ReferenceQueue构造参数

强引用

StrongReference

直接调用

不回收

-

软引用

SoftReference

get方法

gc且内存不足时回收

可选参数

弱引用

WeakReference

get方法

gc时回收

可选参数

虚引用

PhantomReference

无法取得,get方法始终返回null

gc时回收

必传参数

使用的坑

注意软引用 / 弱引用的一个坑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
String s = "string";
WeakReference<String> ref = new WeakReference<>(s);

// 错误写法
if (ref.get() != null) {
ref.get().length(); // 此处可能已经被回收,导致空指针
}

// 正确写法 1
String s1 = ref.get(); // 临时变为强引用
if (s1 != null) {
s1.length();
}

// 正确写法 2
String s2;
if ((s2 = ref.get()) != null) {
s2.length();
}

应用

SoftReference、WeakReference都可以用作缓存,例如Glide图片库使用了WeakReference缓存。

WeakReference还可以用于监控内存,例如LeakCanary。

PhantomReferences和WeakReference类似,区别是PhantomReferences不能获取到目标对象。

PhantomReferences的两种作用:

  • 监控对象是否已回收,以便实现内存敏感的需求,例如可以等待大对象回收后再创建下一个。
  • 代替finalize方法,自行执行回收操作,减小gc压力。

例:LeakCanary在Android中监控内存泄露

LeakCanary可用于检测Android内存泄露,内存追踪使用了WeakReference。

基于LeakCanary 1.5.4源码,流程描述如下:

  1. ActivityRefWatcher:Activity.onDestroy()时,添加Activity对象到RefWatcher监控列表中(创建一个虚引用)。
  2. AndroidWatchExecutor:延时5s。
  3. RefWatcher:通过ReferenceQueue,判断对象是否已回收。
  4. GcTrigger:如果没有回收,触发gc。
  5. RefWatcher:判断对象是否已回收。
  6. AndroidHeapDumper:如果没有回收,可能有内存泄露。调用Android提供的Debug.dumpHprofData(),生成Heap Dump(堆转储文件)。
  7. HeapAnalyzerService:调用haha模块分析内存泄露。

参考: LeakCanary 源码解析

例:PhantomReferences代替finalize方法执行回收操作

Java对象在被gc回收时,finalize方法会被调用。如果在finalize中执行了复杂的任务,会增加gc压力,因此可以借助PhantomReferences自行处理。

代码来自 Phantom References in Java

1
2
3
4
5
6
7
8
9
10
11
12
public class LargeObjectFinalizer extends PhantomReference<Object> {

public LargeObjectFinalizer(
Object referent, ReferenceQueue<? super Object> q) {
super(referent, q);
}

public void finalizeResources() {
// free resources
System.out.println("clearing ...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
List<LargeObjectFinalizer> references = new ArrayList<>();
List<Object> largeObjects = new ArrayList<>();

for (int i = 0; i < 10; ++i) {
Object largeObject = new Object();
largeObjects.add(largeObject);
references.add(new LargeObjectFinalizer(largeObject, referenceQueue));
}

largeObjects = null;
System.gc();

Reference<?> referenceFromQueue;
for (PhantomReference<Object> reference : references) {
System.out.println(reference.isEnqueued());
}

// finalize resources
while ((referenceFromQueue = referenceQueue.poll()) != null) {
((LargeObjectFinalizer)referenceFromQueue).finalizeResources();
referenceFromQueue.clear();
}