Java Reference源码解析
reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互。即可以使用reference对象来引用其它对象,但是最后还是会被垃圾收集器回收。程序有时候也需要在对象回收后被通知,以告知对象的可达性发生变更。
java提供了四种不同类型的引用,引用级别从高到低分别为finalreference,softreference,weakreference,phantomreference。其中finalreference不对外提供使用。每种类型对应着不同级别的可达性。
简介
强引用finalreference
强引用指的是,程序中有直接可达的引用,而不需要通过任何引用对象,如object obj = new object();中,obj为强引用。
软引用softreference
软引用,非强引用,但是可以通过软引用对象来访问。软引用的对象,只有在内存不足的时候(抛出oom异常前),垃圾收集器会决定回收该软引用所指向的对象。软引用通常用于实现内存敏感的缓存。
softreference<object> softref = new softreference<object>(new object());
弱引用weakreference
弱引用,非强引用和软引用,但是可以通过弱引用对象来访问。弱引用的对象,不管内存是否足够,只要被垃圾收集器发现,该引用的对象就会被回收。实际的应用见weakhashmap等。
weakreference<object> weakref = new weakreference<object>(new object());
虚引用phantomreference
虚引用,该引用必须和引用队列(referencequeue)一起使用,一般用于实现追踪垃圾收集器的回收动作,比如在对象被回收的时候,会调用该对象的finalize方法,在使用虚引用可以实现该动作,也更加安全。
object obj = new object(); referencequeue<object> refqueue = new referencequeue<>(); phantomreference<object> phantom = new phantomreference<object>(obj, refqueue); referencequeue
该队列作为引用中的一员,可以和上述三种引用类型组合使用,该队列的作用是:创建reference时,将queue注册到reference中,当该reference所引用的对象被垃圾收集器回收时,会将该reference放到该队列中,相当于一种通知机制。
示例 demo1:
referencequeue queue = new referencequeue(); weakreference reference = new weakreference(new object(), queue); system.out.println(reference); system.gc(); reference reference1 = queue.remove(); system.out.println(reference1);
源码分析
reference和referencequeue
reference内部有几个比较重要的属性
// 用于保存对象的引用,gc会根据不同reference来特别对待 private t referent; // 如果需要通知机制,则保存的对对应的队列 referencequeue<? super t> queue; /* 这个用于实现一个单向循环链表,用以将保存需要由referencehandler处理的引用 */ reference next; static private class lock { }; // 锁,用于同步pending队列的进队和出队 private static lock lock = new lock(); // 此属性保存一个pending的队列,配合上述next一起使用 private static reference pending = null;
状态图
内部类referencehandler
referencehandler作为reference的静态内部类,用于实现将pending队列里面的reference实例依次添加到不同的referencequeue中(取决于reference里面的queue)。该pending的元素由gc负责加入。
注:这里对pending队列进行加锁,个人认为是因为gc线程可能和referencehandler所在的线程并发执行,如gc采用cms并发收集的时候。
如下代码所示
// 此线程在静态块中启动,即一旦使用了reference,则会启动该线程 private static class referencehandler extends thread { public void run() { for (;;) { reference r; synchronized (lock) { if (pending != null) { r = pending; reference rn = r.next; // 从pending中取下一个元素,如果后继为空,则next指向自身 pending = (rn == r) ? null : rn; r.next = r; } else { try { // 没有则等待,后续加入元素会调用lock.notify唤醒 lock.wait(); } catch (interruptedexception x) { } continue; } } // ... referencequeue q = r.queue; // 如果该reference注册了对应的queue,则加入到该queue中 if (q != referencequeue.null) q.enqueue(r); } } }
referencequeue属性
// 用于标识没有注册queue static referencequeue null = new null(); // 用于标识已经处于对应的queue中 static referencequeue enqueued = new null(); static private class lock { }; /* 互斥锁,用于同步referencehandler的enqueue和用户线程操作的remove和poll出队操作 */ private lock lock = new lock(); // 队列 private volatile reference<? extends t> head = null; // 队列中的元素个数 private long queuelength = 0;
referencequeue.enqueue
只会通过reference里要调用该方法,用于将reference放入到当前队列中
boolean enqueue(reference<? extends t> r) { synchronized (r) { // 判断是否已经入队了 if (r.queue == enqueued) return false; synchronized (lock) { r.queue = enqueued; // 单向循环 r.next = (head == null) ? r : head; head = r; queuelength++; if (r instanceof finalreference) { sun.misc.vm.addfinalrefcount(1); } // 通知当前挂起的线程(调用remove时有可能会挂起) lock.notifyall(); return true; } } }
referencequeue.remove
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; for (;;) { // 否则等待,由enqueue时notify唤醒 lock.wait(timeout); r = reallypoll(); if (r != null) return r; if (timeout != 0) return null; } } }
具体执行流程
以上述示例demo1作为分析
// 创建一个引用队列 referencequeue queue = new referencequeue(); // 创建虚引用,此时状态为active,并且reference.pending为空,当前reference.queue = 上面创建的queue,并且next=null weakreference reference = new weakreference(new object(), queue); 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);
应用 - weakhashmap
weakhashmap在使用上和hashmap类型,都是hash + 链表解决冲突,唯一不同点在于前者的key是使用虚引用来实现的,即当进行垃圾回收的时候,就是被回收掉,此时weakhashmap会在下次操作的时候,根据被回收掉的key,从map里面移除掉。
entry
当创建entry的时候,会注册进当前map属性的queue,当key被回收后,则该entry会被放入到queue中,每当操作map的时候,才会将原有的value清除掉。(由expungestaleentries方法来进行,并且没有启动一个单独的线程来处理,没有必要,这样子简化了逻辑以及避免锁的开销)
// 外部weakhashmap属性 private final referencequeue<object> queue = new referencequeue<>(); /* 这里采用了集成weakreference而不是直接使用,是因为当被回收的时候,具体的key是不知道的,这里需要往weakreference额外加入一些属性,以便在被回收后通知时,能够定位到具体的key/value */ private static class entry<k,v> extends weakreference<object> implements map.entry<k,v> { // 这里属性不能加入key,否则会导致存在强引用而不能被视为weakreference回收掉 v value; int hash; entry<k,v> next; entry(object key, v value, referencequeue<object> queue, int hash, entry<k,v> next) { super(key, queue); this.value = value; this.hash = hash; this.next = next; } // ... }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。