You must know the java reference type — reference source code analysis

definition

Reference refers to the reference object itself, and referent refers to the object referenced by reference. The following description will appear in the form of reference and referent.

explain

The reference class works closely with garbage collection, so it cannot be subclassed directly. In short, the inheritance classes of reference are strictly designed, and even the order of member variables cannot be changed. Therefore, it is meaningless to directly inherit the reference class in the code. However, you can inherit subclasses of the reference class.

For example, finalizer inherits from finalreference, and cleaner inherits from phantom reference

Constructor

There are two constructors in the reference class. One needs to pass in the reference queue, and the other does not.

The significance of this queue is to add a judgment mechanism to judge whether objects are recycled by monitoring this queue externally. If an object is about to be recycled, the reference object that references the object will be placed in the queue. By monitoring the queue, you can take out the reference and then deal with the aftermath.

Without this queue, you can only constantly poll the reference object and judge whether the object is recycled by whether the get method returns null (the phantom reference object cannot do this. Its get method always returns null, so it only has a constructor with queue).

The two methods have corresponding use scenarios, and the specific use needs specific analysis. For example, in weakhashmap, query the data of the queue to determine whether any objects will be recycled. Threadlocalmap is processed by judging whether get() is null.

/* -- Constructors -- */
Reference(T referent) {
    this(referent,null);
}

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

Internal members

There are several member variables inside the reference class:

Referent: saves the object pointed to by reference.

private T referent;

Queue: the reference queue associated with the reference object. Is the queue to be notified when an object is about to be recycled. When the object will be recycled, the reference object (not the object referenced by the referent) will be placed in the queue, and then the external program can get the corresponding data by monitoring the queue.

The queue (i.e. ReferenceQueue object) here is nominally a queue. In fact, it is a one-way queue represented by a single linked list. It can be understood that the queue is a linked list, which only stores the current head node, and the subsequent nodes can be maintained by each reference node through next.

volatile ReferenceQueue<? super T> queue;

Next: points to the next reference. Reference is a single linked list structure.

Reference next;

Discovered: indicates the next object of the object to be processed.

/* 当处于active状态: discovered链表中下一个待处理对象
 * 当处于pending状态: pending列表中的下一个对象
 * 其它状态:   NULL
 */
transient private Reference<T> discovered;

Lock: internal synchronization lock object. Used as a synchronization object when operating the pending linked list. Note that this is a static object, which means that all reference objects share the same lock.

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

Pending: a linked list of elements waiting to be added to the queue. Note that this is a static object, which means that all reference objects share the same pending queue.

/* 用来保存那些需要被放入队列中的reference,收集器会把引用添加到这个列表里来,
 * Reference-handler线程会从中移除它们。
 * 这个列表由上面的lock对象锁进行保护。列表使用discovered字段来链接它的元素。
 */
private static Reference<Object> pending = null;

::: warning indicates that the queue uses next to find the next reference, and the pending queue uses discovered to find the next reference.::

Reference status

In the reference class, there is a long comment to describe the state of the internal object referent.

Therefore, the instance has four states: active, pending, enqueued, and inactive. Of course, the pending and enqueued states exist only when the reference instance is created and the reference queue is registered.

When a reference is in the active state, it indicates that it is active and normal. The garbage collector will monitor the referent of the reference. If it is scanned that it has no strong reference Association, it will make a recycling judgment.

If it is determined that recycling is required, judge whether the reference queue is registered, and if so, set the status of the reference to pending. When the reference is in the pending state, it indicates that it is ready to be put into the reference queue. In this state, the objects to be processed will be put into the queue one by one. During this time window, the corresponding reference object is in pending state.

When it enters the enqueued state, it indicates that the referenced instance has been placed in the queue and is ready to be polled by an external thread to obtain the corresponding information. At this point, the pair pointed to by the reference is about to be recycled by the garbage collector.

When it becomes inactive, it indicates that it is completely cold and its life has come to an end. No matter what way you use, you can't save it.

The JVM does not show the definition of such a state, but judges it through next and queue.

Active:如果创建Reference对象时,没有传入ReferenceQueue,queue=ReferenceQueue.NULL。如果有传入,则queue指向传入的Referencequeue队列对象。next == null;

Pending:queue为初始化时传入ReferenceQueue对象;next == this;

Enqueue:queue == ReferenceQueue.ENQUEUED;next为queue中下一个reference对象,或者若为最后一个了next == this;

Inactive:queue == ReferenceQueue.NULL; next == this.

If next = = null, reference is in active state;

If next= null,queue == ReferenceQueue. Null, the reference is in inactive state;

If next= null,queue == ReferenceQueue. Enqueued, the reference is in enqueue status;

If next= null,queue != ReferenceQueue. NULL && queu != ReferenceQueue. If required, the reference is in pending status.

Referencehandler thread

There is a special thread in the reference class called referencehandler, which deals with the reference objects in the pending linked list. Referencehandler class is a static internal class of reference class, which inherits from thread, so this thread is called referencehandler thread.

private static class ReferenceHandler extends Thread {
    // 确保类已经被初始化
    private static void ensureClassInitialized(Class<?> clazz) {
        try {
            Class.forName(clazz.getName(),true,clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
        }
    }

    static {
        // 预加载并初始化 InterruptedException 和 Cleaner 类
        // 来避免出现在循环运行过程中时由于内存不足而无法加载它们		 
        ensureClassInitialized(InterruptedException.class);
        ensureClassInitialized(Cleaner.class);
    }

    ReferenceHandler(ThreadGroup g,String name) {
        super(g,name);
    }

    public void run() {
        // 死循环调用
        while (true) {
            tryHandlePending(true);
        }
    }
}

In fact, this class is also very simple. First, two classes are preloaded, and then the while loop is used in the run method to run the tryhandlerpending method. This method can be roughly judged by its name. It should be used to process the pending linked list. Let's look at its internal code:

static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn,tgn = tg.getParent());
    // 将handler线程注册到根线程组中并设置最高优先级
    Thread handler = new ReferenceHandler(tg,"Reference Handler");
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();

    // 覆盖jvm的默认处理方式
    SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
        @Override
        public boolean tryHandlePendingReference() {
            return tryHandlePending(false);
        }
    });
}

In fact, a referencehandler thread with the highest priority is started in the root thread group in the static code segment, and overrides the default processing method of pending in the JVM. Well, the key point is the sentence try handle pending (false). Next, let's look at the implementation here:

static boolean tryHandlePending(boolean waitForNotify) {
    Reference<Object> r;
    Cleaner c;
    try {
        synchronized (lock) {
            // 如果pending链表不为null,则开始进行处理
            if (pending != null) {
                r = pending;
                // 使用 'instanceof' 有时会导致OOM
                // 所以在将r从链表中摘除时先进行这个操作
                c = r instanceof Cleaner ? (Cleaner) r : null;
                // 移除头结点,将pending指向其后一个节点
                pending = r.discovered;
                // 此时r为原来pending链表的头结点,已经从链表中脱离出来
                r.discovered = null;
            } else {
                // 在锁上等待可能会造成OOM,因为它会试图分配exception对象
                if (waitForNotify) {
                    lock.wait();
                }
                // 重试
                return waitForNotify;
            }
        }
    } catch (OutOfMemoryError x) {
        Thread.yield();
        // 重试
        return true;
    } catch (InterruptedException x) {
        // 重试
        return true;
    }

    // 如果摘除的元素是Cleaner类型,则执行其clean方法
    if (c != null) {
        c.clean();
        return true;
    }

    ReferenceQueue<? super Object> q = r.queue;
    // 最后,如果其引用队列不为空,则将该元素入队
    if (q != ReferenceQueue.NULL) q.enqueue(r);
    return true;
}

Therefore, the whole process here is to extract the head node of the pending linked list. If it is a cleaner, execute the clean operation, otherwise it will be queued.

common method

/**
  * 返回引用指向的对象,如果referent已经被程序或者垃圾回收器清理,则返回null。
  */
public T get() {
    return this.referent;
}

/**
  * 清理referent对象,调用该方法不会使得这个对象进入Enqueued状态。
  */
public void clear() {
    this.referent = null;
}

/**
  * 判断该reference是否已经入队。
  */
public boolean isEnqueued() {
    return (this.queue == ReferenceQueue.ENQUEUED);
}

/**
  * 将该引用添加到其注册的引用队列中。
  * 如果reference成功入队则返回true,如果它已经在队列中或者创建时没有注册队列则返回false
  */
public boolean enqueue() {
    return this.queue.enqueue(this);
}

The reference class is used to wrap objects. Through close cooperation with the JVM, the wrapped objects can be specially processed by the JVM. Therefore, using the reference object can enable us to control the object life cycle in a finer granularity.

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
分享
二维码
< <上一篇
下一篇>>