System.out.println(reference);
// 当GC执行后,由于是弱引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
System.gc();
/* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */
/* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);
那这个可以用来干什么了?
可以用来检测内存泄露, github 上面 的 leekCanary 就是采用这种原理来检测的。
-
监听 Activity 的生命周期
-
在 onDestroy 的时候,创建相应的 Reference 和 ReferenceQueue,并启动后台进程去检测
-
一段时间之后,从 ReferenceQueue 读取,若读取不到相应 activity 的 Reference,有可能发生泄露了,这个时候,再促发 gc,一段时间之后,再去读取,若在从 ReferenceQueue 还是读取不到相应 activity 的 Reference,可以断定是发生内存泄露了
-
发生内存泄露之后,dump,分析 hprof 文件,找到泄露路径
主要内存成员变量
private T referent;
volatile ReferenceQueue<? super T> queue;
/* When active: NULL
-
pending: this
-
Enqueued: next reference in queue (or this if last)
-
Inactive: this
*/
@SuppressWarnings(“rawtypes”)
Reference next;
transient private Reference discovered; /* used by VM */
/* List of References waiting to be enqueued. The collector adds
-
References to this list, while the Reference-handler thread removes
-
them. This list is protected by the above lock object. The
-
list uses the discovered field to link its elements.
*/
private static Reference pending = null;
-
referent表示其引用的对象,即在构造的时候需要被包装在其中的对象。
-
queue 是对象即将被回收时所要通知的队列。当对象即将被回收时,整个reference对象,而不仅仅是被回收的对象,会被放到queue 里面,然后外部程序即可通过监控这个 queue 即可拿到相应的数据了。
-
next 即当前引用节点所存储的下一个即将被处理的节点。但 next 仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的 ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。
-
discovered 表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered 不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM 使用的。
-
pending 是等待被入队的引用列表。JVM 收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用 discovered 字段来连接它下一个元素(即 pending 的下一个元素就是discovered对象。r = pending; pending = r.discovered)。
接下来,我们来看一下 Reference 的静态代码块
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, “Reference Handler”);
/* If there were a special system-only priority greater than
- MAX_PRIORITY, it would be used here
*/
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
我们,当 Reference 类被加载的时候,会执行静态代码块。在静态代码块里面,会启动 ReferenceHandler 线程,并设置线程的级别为最大级别, Thread.MAX_PRIORITY。
接下来我们来看一下 ReferenceHandler 这个类,可以看到 run 方法里面是一个死循环,我们主要关注 tryHandlePending 方法就 Ok 了
private static class ReferenceHandler extends Thread {
----- // 核心代码如下
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference r;
Cleaner c;
try {
synchronized (lock) {
// 检查 pending 是否为 null,不为 null,制定 pending enqueue
if (pending != null) {
r = pending;
// ‘instanceof’ might throw OutOfMemoryError sometimes
// so do this before un-linking ‘r’ from the ‘pending’ chain…
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink ‘r’ from ‘pending’ chain
pending = r.discovered;
r.discovered = null;
} else { // 为 null。等待
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case ‘r instanceof Cleaner’ above
// persistently throws OOME for some time…
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue®;
return true;
}
在 tryHandlePending 方法里面,检查 pending 是否为 null,如果pending不为 null,则将 pending 进行 enqueue,否则线程进入 wait 状态。
问题来了,我们从 Reference 源码中发现没有给 discovered和 pending 赋值的地方,那 pending和 discovered 到底是谁给他们赋值的。
我们回头再来看一下注释:简单来说,垃圾回收器会把 References 添加进入,Reference-handler thread 会移除它,即 discovered和 pending 是由垃圾回收器进行赋值的。
/* List of References waiting to be enqueued. The collector adds
-
References to this list, while the Reference-handler thread removes
-
them. This list is protected by the above lock object. The
-
list uses the discovered field to link its elements.
*/
private static Reference pending = null;
接下来,我们在来看一下 ReferenceQueue 的 enqueue 方法
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn’t already been
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
// queue 为 null 或者 queue 已经被回收了,直接返回
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 将 Reference 的状态置为 Enqueued,表示已经被回收
r.queue = ENQUEUED;
// 接着,将 Reference 插入到链表
// 判断当前链表是否为 null,不为 null,将 r.next 指向 head,为 null,head 直接指向 r
r.next = (head == null) ? r : head;
// head 指针指向 r
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
-
判断 queue 为 null 或者 queue 已经被回收了,直接返回
-
若 queue 不为 null,将 r (Reference) 的状态置为 Enqueued,表示已经被回收
-
将 Reference 插入到 queue 的头部
Reference 和 ReferenceQueue 的源码分析到此为止
4种引用
我们都知道在Java中有4种引用,这四种引用从高到低分别为:
- StrongReference
这个引用在Java中没有相应的类与之对应,但是强引用比较普遍,例如:Object obj = new Object();这里的obj就是要给强引用,如果一个对象具有强引用,则垃圾回收器始终不会回收此对象。当内存不足时,JVM情愿抛出OOM异常使程序异常终止也不会靠回收强引用的对象来解决内存不足的问题。
分享
1、算法大厂——字节跳动面试题
2、2000页互联网Java面试题大全
3、高阶必备,算法学习
4种引用
我们都知道在Java中有4种引用,这四种引用从高到低分别为:
- StrongReference
这个引用在Java中没有相应的类与之对应,但是强引用比较普遍,例如:Object obj = new Object();这里的obj就是要给强引用,如果一个对象具有强引用,则垃圾回收器始终不会回收此对象。当内存不足时,JVM情愿抛出OOM异常使程序异常终止也不会靠回收强引用的对象来解决内存不足的问题。
分享
1、算法大厂——字节跳动面试题
[外链图片转存中…(img-swAwgaUc-1719261880247)]
2、2000页互联网Java面试题大全
[外链图片转存中…(img-YUJlenKd-1719261880247)]
3、高阶必备,算法学习
[外链图片转存中…(img-yVYeONr1-1719261880248)]