通过ReferenceQueue来监听被GC
实例代码
class ReferenceQueueActivity : Activity() {
private val queue = ReferenceQueue<Person?>()
private var mWeakReference: WeakReference<Person?>? = null
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var s: Person? = Person()
mWeakReference = WeakReference(s, queue)
s = null
System.gc()
val button = Button(this)
button.text = "poll"
button.setOnClickListener {
System.gc()
println("mWeakReference.get()" + mWeakReference!!.get())
val reference = queue.poll()
if (reference != null) {
println("reference" + reference.get())
if (mWeakReference === reference) {
println("哈哈监听到啦!")
}
}
}
setContentView(button)
}
}
先创建一个Person然后把它赋值为空,点击button会发现如下log
com.seekting.demo2018 I/System.out: mWeakReference.get()null
com.seekting.demo2018 I/System.out: referencenull
com.seekting.demo2018 I/System.out: 哈哈监听到啦!
com.seekting.demo2018 I/System.out: mWeakReference.get()null
会发现poll出来一个WeakReference和mWeakReference相等,而此时mWeakReference里的引用已经为空了
通过这个能监听到一个对象被释放。
源码解析
ReferenceQueue源码
ReferenceQueue源码不多,强烈建议看
首先看成员变量:
static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();
static private class Lock { };
private Lock lock = new Lock();
private volatile Reference<? extends T> head = null;
private long queueLength = 0;
lock:synchronized用到的一个锁
head:指向链表头部的一个引用(它是后进先出的结构,有点像栈~)
queueLength:链表长度
重点讲讲NULL和ENQUEUED(enqueue()操作)
Reference类里有一个成员
volatile ReferenceQueue<? super T> queue;
而这个成员在ReferenceQueue里多被灵活应用:
1. 当你通过WeakReference(s, queue)构造时
它表示,如果WeakReference是入队,当然是找指定的ReferenceQueue,这里就是queue对象
2. 当你通过当你通过WeakReference(s)构造时
它表示,不需要入队,也就是说内存被回收时不会入队
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
Reference的构造告诉我们它会用ReferenceQueue.NULL;而Null也是ReferenceQueue的子类
它的实现就是返回false,啥也不做,就等于内存被回收时不入队,也就是不用监听内存回收
private static class Null<S> extends ReferenceQueue<S> {
boolean enqueue(Reference<? extends S> r) {
return false;
}
}
相比1情况,如果传的是一个ReferenceQueue,那它会走真实的入队操作
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ synchronized (lock) { ReferenceQueue<?> queue = r.queue; //如果该Reference的queue是空就返回false //如果是ENQUEUED就返回false if ((queue == NULL) || (queue == ENQUEUED)) { return false; } assert queue == this;//断言这里的queue是自己 r.queue = ENQUEUED;//这里表示它已经入队了,通过ENQUEUED来防止它再入队 r.next = (head == null) ? r : head;//r->head->1->2 head = r;//head->0->1->2 queueLength++;//队列长度+1 if (r instanceof FinalReference) { sun.misc.VM.addFinalRefCount(1); } lock.notifyAll(); return true; } }
入队代码很容易读懂:
1. 如果Reference的queue是Null或是ENQUEUED就返回false不操作
2. 断言这里的queue是自己
3. 新加的节点是会变成头节点,有点像栈的结构
出队操作poll()
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
private Reference<? extends T> reallyPoll() {
Reference<? extends T> r = head;//拿到头节点
if (r != null) {
head = (r.next == r) ?
null ://头节点的下一个是自己就说明是最后一个节点了,拿走了,head当然为空了
r.next;//否则头节点就是以前的头节点的下一个节点:后进先出
r.queue = NULL;//把它的queue置为空,也就是再也没有机会入队了
r.next = r;//把该节点的下一个节点引用截断
queueLength--;//长度-1
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
注意出队入队的时候都加了synchronized同步