You must know the java reference type — a detailed explanation of the source code of ReferenceQueue

definition

ReferenceQueue is a reference queue used to store reference objects to be recycled.

explain

For soft references, weak references and virtual references, if we want to be notified when an object is recycled by the garbage collector for additional processing, we need to use the reference queue.

When an object is scanned by the garbage collector to be recycled, its corresponding reference wrapper class, that is, the reference object, will be placed in its registered reference queue. The corresponding object information can be obtained from the queue and additional processing can be performed at the same time. For example, reverse operation, data cleaning, resource release, etc.

Use examples

public class ReferenceQueueTest {
    private static ReferenceQueue<byte[]> rq = new ReferenceQueue<>();
    private static int _1M = 1024 * 1024;

    public static void main(String[] args) {
        Object value = new Object();
        Map<WeakReference<byte[]>,Object> map = new HashMap<>();
        Thread thread = new Thread(ReferenceQueueTest::run);
        thread.setDaemon(true);
        thread.start();

        for(int i = 0;i < 100;i++) {
            byte[] bytes = new byte[_1M];
            WeakReference<byte[]> weakReference = new WeakReference<>(bytes,rq);
            map.put(weakReference,value);
        }
        System.out.println("map.size->" + map.size());
        
        int aliveNum = 0;
        for (Map.Entry<WeakReference<byte[]>,Object> entry : map.entrySet()){
            if (entry != null){
                if (entry.getKey().get() != null){
                    aliveNum++;
                }
            }
        }
        System.out.println("100个对象中存活的对象数量:" + aliveNum);
    }

    private static void run() {
        try {
            int n = 0;
            WeakReference k;
            while ((k = (WeakReference) rq.remove()) != null) {
                System.out.println((++n) + "回收了:" + k);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Here is a little chestnut. In the main method, a thread is created to obtain elements from the reference queue using an endless loop to monitor the state of the object being recycled. Then, the loop adds 100 mapping relationships to the map. The following is the running result:

...前面省略了大量相似输出
85回收了:java.lang.ref.WeakReference@7106e68e
86回收了:java.lang.ref.WeakReference@1f17ae12
87回收了:java.lang.ref.WeakReference@c4437c4
map.size->100
100个对象中存活的对象数量:12

By using ReferenceQueue together, you can better monitor the survival status of objects.

Member variable

There are few internal member variables in ReferenceQueue, mainly as follows:

static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();

There are two static member variables used as special markers, one is null and the other is enqueue. The ReferenceQueue in the previous article Null and ReferenceQueue Enqueued is these two guys.

Let's see what null looks like:

private static class Null<S> extends ReferenceQueue<S> {
    boolean enqueue(Reference<? extends S> r) {
        return false;
    }
}

It simply inherits a class of ReferenceQueue, emmm. Why not directly new a ReferenceQueue? There is a natural reason here. If ReferenceQueue is used directly, it may lead to misoperation of the null and enqueued variables, because the enqueue method in ReferenceQueue needs to use lock object lock. This method is overwritten and returns false directly, which avoids the possibility of misuse and unnecessary waste of resources.

static private class Lock { };
private Lock lock = new Lock();

Like reference, there is a lock object for synchronization.

private volatile Reference<? extends T> head = null;

Head is used to save the head node of the queue. Because reference is a single linked list structure, you only need to save the head node.

private long queueLength = 0;

Queuelength is used to save the queue length. It is + 1 when adding elements and - 1 when removing elements. Because synchronized is used for synchronization during addition and removal operations, there is no need to worry about whether multi-threaded modification will make mistakes.

Internal method

// 这个方法仅会被Reference类调用
boolean enqueue(Reference<? extends T> r) { 
    synchronized (lock) {
        // 检测从获取这个锁之后,该Reference没有入队,并且没有被移除
        ReferenceQueue<?> queue = r.queue;
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;
        // 将reference的queue标记为ENQUEUED
        r.queue = ENQUEUED;
        // 将r设置为链表的头结点
        r.next = (head == null) ? r : head;
        head = r;
        queueLength++;
        // 如果r的FinalReference类型,则将FinalRef+1
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        lock.notifyAll();
        return true;
    }
}

Here is the queue joining method. The lock object lock is used for synchronization, the incoming R is added to the queue, and the head node is reset to the incoming node.

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 : r.next;
        r.queue = NULL;
        r.next = r;
        queueLength--;
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(-1);
        }
        return r;
    }
    return null;
}

The poll method pops up the header node. Well, yes, the pop-up is the head node rather than the tail node. Nominally, it is called ReferenceQueue. In fact, it is a referencestack (funny). Surprise or surprise.

/**
  * 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象或者超时才会返回
  * timeout时间的单位是毫秒
  */
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 = reallyPoll();
        if (r != null) return r;
        long start = (timeout == 0) ? 0 : System.nanoTime();
        // 死循环,直到取到数据或者超时
        for (;;) {
            lock.wait(timeout);
            r = reallyPoll();
            if (r != null) return r;
            if (timeout != 0) {
                // System.nanoTime方法返回的是纳秒,1毫秒=1纳秒*1000*1000
                long end = System.nanoTime();
                timeout -= (end - start) / 1000_000;
                if (timeout <= 0) return null;
                start = end;
            }
        }
    }
}

/**
 * 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象才会返回
 */
public Reference<? extends T> remove() throws InterruptedException {
	return remove(0);
}

The two methods here remove the first node from the queue. Unlike poll, it blocks until it times out or gets a reference object.

Smart, you may think that when you call the remove method, if the queue is empty, it will always block and occupy the lock object lock. At this time, if there are references that need to join the queue, won't they be able to enter?

Well, that's true, but note that enqueue is only called for reference. In the public method enqueue of reference, the reference can be directly queued, but the virtual machine, as the manager of the program, does not eat this set, but inserts the reference object in other ways. Therefore, in the previous chestnut, the remove method is called in an endless loop, Does not block references from entering the queue.

Application scenario

ReferenceQueue is generally used in conjunction with softreference, WeakReference or phantom reference. After registering the reference object to be concerned to the reference queue, you can monitor the queue to determine whether the object to be concerned is recycled, so as to execute the corresponding method.

Main usage scenarios:

1. Use reference queue for data monitoring, similar to the previous usage of chestnut.

2. Reverse operation of queue monitoring

Reverse operation means that if a data has changed, you can get the relevant data in reverse through the reference object for subsequent processing. There is a little chestnut below:

public class TestB {

    private static ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
    private static int _1M = 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
        final Map<Object,MyWeakReference> hashMap = new HashMap<>();
        Thread thread = new Thread(() -> {
            try {
                int n = 0;
                MyWeakReference k;
                while(null != (k = (MyWeakReference) referenceQueue.remove())) {
                    System.out.println((++n) + "回收了:" + k);
                    //反向获取,移除对应的entry
                    hashMap.remove(k.key);
                    //额外对key对象作其它处理,比如关闭流,通知操作等
                }
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.setDaemon(true);
        thread.start();

        for(int i = 0;i < 10000;i++) {
            byte[] bytesKey = new byte[_1M];
            byte[] bytesValue = new byte[_1M];
            hashMap.put(bytesKey,new MyWeakReference(bytesKey,bytesValue,referenceQueue));
        }
    }

    static class MyWeakReference extends WeakReference<byte[]> {
        private Object key;
        MyWeakReference(Object key,byte[] referent,ReferenceQueue<? super byte[]> q) {
            super(referent,q);
            this.key = key;
        }
    }
}

Here, after the ReferenceQueue monitors that a reference is recycled, the corresponding value is inversely obtained through the map, and then the resource is released.

Summary

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>