关于Java源码的学习可以参考《Java源码分析》:ReferenceQueue、Reference及其子类。这里看一下Android api27源码中的引用相关的类。
学习之前我们做个约定:
Reference ref = new WeakReference<String>("hello");
我们称ref
为引用对象,"hello"
为实际对象。
Reference
Reference类与GC有紧密联系,不要直接继承Reference类。
Reference类中部分代码是通过GC操作的,我们只关注表层的内容。涉及到的代码如下:
volatile T referent; /* Treated specially by GC ,由GC操作*/
final ReferenceQueue<? super T> queue;
Reference queueNext;
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = queue;
}
public T get() {
return getReferent();
}
public void clear() {
clearReferent();
}
public boolean isEnqueued() {
return queue != null && queue.isEnqueued(this);
}
public boolean enqueue() {
return queue != null && queue.enqueue(this);
}
GC在清除实际对象时,若queue不为null,则会将该引用对象enqueue。
queueNext字段
Reference类本身可以理解为一个链表的节点。每个Reference对象只可以enqueue一次。
若该Reference对象已经enqueue,并且未被remove,则queueNext指向下一个Reference对象。若被remove,则queueNext=ReferenceQueue.sQueueNextUnenqueued
,之后无法再次enqueue。
get()
返回该Reference的实际对象,若被释放,则返回null。
实际对象可能被GC释放,也可以通过调用clear()
手动释放。
clear()
释放实际对象。该方法不会使引用对象enqueue。
该方法由java代码调用,GC释放实际对象不会调用该方法。
isEnqueued()
判断该引用对象是否已经enqueue。包括通过GC enqueue和手动调用enqueue()
方法。
enqueue()
将该引用对象加入队列中。
该方法由java代码调用,GC引起的enqueue不会调用该方法。
ReferenceQueue
GC在检测到实际对象的可达性发生适当的变化后,会将引用对象追加到它所注册的ReferenceQueue中。
该类公共方法有:
public ReferenceQueue() { }
public Reference<? extends T> poll() {
synchronized (lock) {
if (head == null)
return null;
return reallyPollLocked();
}
}
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException
{
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
synchronized (lock) {
Reference<? extends T> r = reallyPollLocked();
if (r != null) return r;
long start = (timeout == 0) ? 0 : System.nanoTime();
for (;;) {
lock.wait(timeout);
r = reallyPollLocked();
if (r != null) return r;
if (timeout != 0) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
除了第一个空的构造函数,剩下的3个都是弹出头结点的方法。poll
是非阻塞方法,remove
是阻塞方法。
那么如何保证每个Reference至多enqueue一次呢?
//标识引用对象已经enqueue过的flag
private static final Reference sQueueNextUnenqueued = new PhantomReference(null, null);
private Reference<? extends T> reallyPollLocked() {
if (head != null) {
Reference<? extends T> r = head;
if (head == tail) {
tail = null;
head = null;
} else {
head = head.queueNext;
}
// Update queueNext to indicate that the reference has been
// enqueued, but is now removed from the queue.
r.queueNext = sQueueNextUnenqueued;//每个出队的Reference,queueNext被设置为sQueueNextUnenqueued。
return r;
}
return null;
}
private boolean enqueueLocked(Reference<? extends T> r) {
// Verify the reference has not already been enqueued.
if (r.queueNext != null) {//queueNext!=null表示是第二次enqueue请求
return false;
}
...
}
关于SoftReference,Android源码中有这么一句:
避免使用软引用做缓存:
实际使用中,用软引用做缓存并不高效,运行时并没有太多的信息来决定哪些软引用该释放,哪些该保留。更加致命的是,运行时在面临清除软引用和增大堆尺寸的选择时,并不知道该怎么做。
对软引用的信息的缺乏限制了软引用的使用,过早释放会导致不必要的工作,过晚释放则会浪费内存。
应用应该使用LruCache来替代软引用,它有更高效的回收策略。
最后,关于开头提到的文章中的例子做个修改,以更全面了解Reference:
public class JavaDemo {
private static final ReferenceQueue<Data> REFERENCE_QUEUE = new ReferenceQueue<>();
private static void checkQueue() throws InterruptedException {
Reference<? extends Data> inq = REFERENCE_QUEUE.remove();
if (inq != null) {
System.out.println("queue---" + inq.getClass().getSimpleName());
}
}
public static void main(String[] args) {
//另起一个线程监视REFERENCE_QUEUE
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
checkQueue();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);
thread.start();
///////////////////////////////////////////////////////////////////////////////////
/*
* SoftReference:在内存不足时才会回收这样软引用对象
* */
Deque<SoftReference<Data>> sa = new LinkedList<>();
for (int i = 0; i < 5; i++) {
sa.add(new MySoftReference(new Data("Soft " + i), REFERENCE_QUEUE));
System.out.println("created---" + sa.getLast().get());
}
//由于无法触发内存不足,这里手动回收软引用。
for (SoftReference<Data> reference : sa) {
reference.clear();
//clear()方法不会enqueue,所以需要手动入队。
reference.enqueue();
}
///////////////////////////////////////////////////////////////////////////////////
/*
* WeakReference:在GC发现只具有弱引用的对象会立即对其会回收
* */
Deque<MyWeakReference<Data>> wa = new LinkedList<>();
for (int i = 0; i < 5; i++) {
wa.add(new MyWeakReference(new Data("Weak " + i), REFERENCE_QUEUE));
System.out.println("created---" + wa.getLast().get());
}
///////////////////////////////////////////////////////////////////////////////////
Deque<MyPhantomReference<Data>> pa = new LinkedList<>();
for (int i = 0; i < 5; i++) {
pa.add(new MyPhantomReference(new Data("Phantom " + i), REFERENCE_QUEUE));
System.out.println("created---" + pa.getLast());
}
///////////////////////////////////////////////////////////////////////////////////
// System.gc(); //第一次gc
///////////////////////////////////////////////////////////////////////////////////
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.gc();//第二次gc
///////////////////////////////////////////////////////////////////////////////////
// sa.clear();
// pa.clear();
// wa.clear();
// System.gc();//第三次gc
///////////////////////////////////////////////////////////////////////////////////
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.gc();//第四次gc
}
}
class MySoftReference<T> extends SoftReference<T> {
public MySoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
@Override
protected void finalize() {
System.out.println("finalize---MySoftReference");
}
}
class MyWeakReference<T> extends WeakReference<T> {
MyWeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
@Override
protected void finalize() {
System.out.println("finalize---MyWeakReference");
}
}
class MyPhantomReference<T> extends PhantomReference<T> {
MyPhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
@Override
protected void finalize() {
System.out.println("finalize---MyPhantomReference");
}
}
class Data {
private final String id;
Data(String id) {
this.id = id;
}
@Override
public String toString() {
return id;
}
@Override
protected void finalize() {
System.out.println("finalize---" + id);
}
}
此时输出如下:
created---Soft 0
created---Soft 1
created---Soft 2
created---Soft 3
created---Soft 4
queue---MySoftReference
queue---MySoftReference
queue---MySoftReference
queue---MySoftReference
queue---MySoftReference
created---Weak 0
created---Weak 1
created---Weak 2
created---Weak 3
created---Weak 4
created---demo.sollian.com.demo.MyPhantomReference@677327b6
created---demo.sollian.com.demo.MyPhantomReference@14ae5a5
created---demo.sollian.com.demo.MyPhantomReference@7f31245a
created---demo.sollian.com.demo.MyPhantomReference@6d6f6e28
created---demo.sollian.com.demo.MyPhantomReference@135fbaa4
除了手动将软引用对象clear并enqueue,其他的看不到对象回收的迹象。
接下来打开第一次gc,
System.gc(); //第一次gc
新增的输出如下:
...
finalize---Soft 0
finalize---Soft 1
finalize---Soft 2
finalize---Weak 2
finalize---Phantom 4
finalize---Phantom 3
finalize---Phantom 2
finalize---Phantom 1
finalize---Phantom 0
finalize---Weak 4
finalize---Weak 3
finalize---Weak 1
finalize---Weak 0
finalize---Soft 4
finalize---Soft 3
queue---MyWeakReference
queue---MyWeakReference
queue---MyWeakReference
queue---MyWeakReference
queue---MyWeakReference
可以看到软引用、弱引用、虚引用的实际对象都已被回收,执行了finalize方法;并且弱引用对象enqueue。
打开第二次gc:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.gc();//第二次gc
新增的输出如下:
...
queue---MyPhantomReference
queue---MyPhantomReference
queue---MyPhantomReference
queue---MyPhantomReference
queue---MyPhantomReference
此时虚应用对象enqueue。
至此,对实际引用的释放处理已经彻底走完。
接下来释放引用对象。打开第三次gc:
sa.clear();
pa.clear();
wa.clear();
System.gc();//第三次gc
新增的输出如下:
...
finalize---MyWeakReference
可以看到释放了一个弱引用对象。
打开第四次gc:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.gc();//第四次gc
新增的输出如下:
...
finalize---MySoftReference
finalize---MySoftReference
finalize---MySoftReference
finalize---MySoftReference
finalize---MyWeakReference
finalize---MyWeakReference
finalize---MyWeakReference
finalize---MySoftReference
finalize---MyWeakReference
finalize---MyPhantomReference
finalize---MyPhantomReference
finalize---MyPhantomReference
finalize---MyPhantomReference
finalize---MyPhantomReference
可以看到释放了其他的引用对象。
网友评论