Java 引用类型

不仅仅有强引用

结构


非强引用都继承java.lang.ref.Reference<T>抽象类
内部包装一个对象引用,以及可选注册一个引用队列
对象垃圾回收后,会把整个引用放入队列,这样外部逻辑就可以知道被回收,可以趁此机会做后续处理
引用队列的好处是可以方便的知道回收行为,如果不使用队列,判断回收需要外部逻辑轮询是否能get出原对象

Reference.java
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
public abstract class Reference<T> {
private T referent;
volatile ReferenceQueue<? super T> queue;
// 链表结构,引用本身将被放入队列,引用作为链表节点
Reference next;

// 不使用队列
Reference(T referent) {
this(referent, null);
}

// 附带队列
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

public T get() {
return this.referent;
}

// 手动清空引用对象,只供用户调用,操作不会导致放入队列,虚拟机直接清理不调用这个方法
public void clear() {
this.referent = null;
}
// 手动放入队列,只供用户调用
public boolean enqueue() {
return this.queue.enqueue(this);
}

// 判断引用对象是否已经被回收入队
public boolean isEnqueued() {
return (this.queue == ReferenceQueue.ENQUEUED);
}

//...
}

引用分类


强引用(Strong Reference)

最常见的引用方式。强引用代表对象在被使用,不会被回收

软引用(Soft Reference)

  • java.lang.ref.SoftReference
  • 一定程度上阻止回收,只要内存还足够,就不会被回收

额外维护时间机制,综合考虑时间和空间因素决定是否回收

  • 时间 clock - timestamp 计算多久没访问
  • 空间 freespace * SoftRefLRUPolicyMSPerMB 计算剩余空间容忍软引用生存时间,直观上看空间越多,对空闲软引用越宽容
SoftReference.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SoftReference<T> extends Reference<T> {
// 上次GC时间
static private long clock;
// 上次访问时间
private long timestamp;

public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}

public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}

// 每次访问更新时间戳为GC时间
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}

反射方法调用多次后,JVM会动态生成临时类优化性能,动态类就是使用软引用

弱引用(Weak Reference)

  • java.lang.ref.WeakReference
  • 不会阻止回收,但是gc线程优先级比较低不一定很快回收掉,提供访问即如果还没回收掉就再次启用
  • 弱引用不会劫持对象,强引用断开,可能get出null

无特殊逻辑,队列用不用都行

WeakReference.java
1
2
3
4
5
6
7
8
9
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}

public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}

虚引用(Phantom Reference)

  • java.lang.ref.PhantomReference
  • 形同虚设,不会影响回收,必须配合引用队列,用于跟踪对象GC活动
  • 原引用对象不可访问,get方法返回null,即不能重建强引用
PhantomReference.java
1
2
3
4
5
6
7
8
9
10
11
public class PhantomReference<T> extends Reference<T> {
// 永远返回null,无法获取
public T get() {
return null;
}

// 必须使用队列
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}

非强引用的作用


强引用下对象不会被回收,如果需要释放对象

  • 没出作用域,还持有对象强引用,解决这种情况方法是手动置为null提前释放
  • 出了作用域,对象还被外围集合强引用持有,解决这种情况方法是手动从集合类移除

非强引用提供了一种柔和的处理方式,如果一个对象上只有非强引用,虽然也是引用但有可能被回收,实现一种自动释放的效果

WeakHashMap


WeakHashMap实现了自动清理,是弱引用的典型应用
普通的HashMap是强引用。即便没有人使用Map内的值,还是被Map强引用着,不会被回收,只能手动从Map中删除

  • 如果忘了手动删除,那么可能内存泄漏
  • 如果删除早了,可能正在被使用中,那么数据丢失

自动清理原理是key利用弱引用自动清理,Entry放入引用队列,用户操作触发Entry清理

底层的Entry实现了WeakReference,虽然Entry同时有key和value,但从构造函数可知只有key使用了弱引用
当key所引用的对象没有强引用时,key被回收,此时Entry中获取key会得到null

WeakHashMap$Entry.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;

Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
//只用key构建弱引用
super(key, queue);
//value是正常的强引用
this.value = value;
this.hash = hash;
this.next = next;
}
public K getKey() {
//通过reference获取key
return (K) WeakHashMap.unmaskNull(get());
}
//...
}

Key被回收后,Entry对象还在,相当于在Map结构中有很多key变成了null值,此时外界通过原来的Key已经无法查出数据
清除Entry这一任务由正常操作触发,比如put,get,size都要获取底层存储数组,而这里会预先清理一遍
Key被回收时,Map的大小并没有发生变化,但是外界调用size时,先进行清理大小发生变化,因此外界可以正确获取当前大小

WeakHashMap.java
1
2
3
4
5
6
7
8
9
10
public int size() {
if (size == 0)
return 0;
expungeStaleEntries();
return size;
}
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}

清理的过程就是遍历引用队列,然后查找到底层数组,再从链表中移除
从map中移除后,还把Entry的value置为null,彻底解除了map对value的引用关系

WeakHashMap.java
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
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
//同一时刻只允许一个线程清理操作,加锁保护
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);

Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
//寻找的是引用,直接等于判定
if (p == e) {
//Entry从链表上摘除
if (prev == e)
table[i] = next;
else
prev.next = next;
//提前释放value并且调整大小
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}