java中的Reference
Posted 张紫荣
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中的Reference相关的知识,希望对你有一定的参考价值。
这两天又重新学习了一下Reference,根据网上的资源做了汇总。
Java中的引用主要有4种:
强引用 StrongReference: Object obj = new Object(); obj就为一个强引用,obj=null后, 该对象可能会被JVM回收
软引用 SoftReference: 在内存不够用的时候,才会回收软引用的对象。
Object obj = new Object();
SoftReference<Object> softref = new SoftReference<Object>(obj);
obj = null;
弱引用 WeakReference: new出来的对象没有强引用连接时,下一次GC时,就会回收该对象。
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<Object>(obj);
obj = null;
虚引用 PhantomReference: 与要与ReferenceQueue配合使用,它的get()方法永远返回null
JDK中的 java.lang.ref包:
java.lang.ref包下主要都是reference相关的类,主要包括:
FinalReference: 代表强引用,使没法直接使用。
Finalizer:FinalReference的子类,主要处理finalize相关的工作
PhantomReference: 虚引用
Reference: 引用基类,abstract的
ReferenceQueue: 引用轨迹队列
SoftReference:软引用
WeakedReference: 弱引用
ReferenceQueue
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中。
static ReferenceQueue NULL = new Null(); //初始化为null static ReferenceQueue ENQUEUED = new Null(); static private class Lock { }; private Lock lock = new Lock(); //初始化为null private volatile Reference<? extends T> head = null; //Queue的header设定为null private long queueLength = 0;
queue主要有几种操作: enqueue, poll(非阻塞), remove(阻塞)
enqueue的操作是添加一个对象到队列中。
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ //本方法只能在Reference类中调用 synchronized (r) { //防止将同一个Reference两次入队列 if (r.queue == ENQUEUED) return false; synchronized (lock) { //该对象入队列后,将该对象的queue对象置为 ENQUEUED r.queue = ENQUEUED; r.next = (head == null) ? r : head; head = r; queueLength++; if (r instanceof FinalReference) { sun.misc.VM.addFinalRefCount(1); } lock.notifyAll(); return true; } } }
入队列的实际操作就是将Reference对象r放到header的第一位
poll的方法为:public Reference<? extends T> poll(),最终会调用 private Reference<? extends T> reallyPoll();具体操作与添加相反,若队列没有什值,直接返回null。
remove的方法:public Reference<? extends T> remove(),最终会调用public Reference<? extends T> remove(long 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; //死循环 for (;;) { lock.wait(timeout); r = reallyPoll(); if (r != null) return r; //若传递的等待时间不为0,说明等该timeout后也没有reference入队列,返回null, //若timeout为0,则循环,直接队列有数据 if (timeout != 0) return null; } } }
SoftReference
SoftReference继承了抽象类Reference,自己内容有两个属性:
/** * Timestamp clock, updated by the garbage collector */ //GC 会更新这一个静态变量的值 static private long clock; /** * Timestamp updated by each invocation of the get method. The VM may use * this field when selecting soft references to be cleared, but it is not * required to do so. */ //每次调用get方法时会更新timestampe // this.timestamp = clock; private long timestamp;
主要的逻辑还是在Reference类中:
Reference提供了两个构造方法:
Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
同时有一些私有的属性:
private T referent; /* Treated specially by GC */ ReferenceQueue<? super T> queue; Reference next; transient private Reference<T> discovered; /* used by VM */ static private class Lock { }; private static Lock lock = new Lock();
最有意思的是有段static的代码块
/* 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. */ //static类型,全局只有一个,GC一个Reference时,会把该Reference放到pending中,由于Reference有一个next属性,该处可能会是一些被GC过的引用的队列 private static Reference pending = null; /* High-priority thread to enqueue pending References */ private static class ReferenceHandler extends Thread { ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { for (;;) { Reference r; synchronized (lock) { if (pending != null) { //若pending不为null,将每个reference取出, r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { //若pending为null,等侍 try { lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } ReferenceQueue q = r.queue; //找到该Reference对象中的Queue,将自己添加到ReferenceQueue中 //这样就实现了软引用对象被回收后,在ReferenceQueue中就可以获取到。 if (q != ReferenceQueue.NULL) q.enqueue(r); } } } //启动一个线程(Reference Handler)处理引用 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(); }
其它的一些操作如:get, clear, isEnqueued, enqueue,都是简单的操作。
WeakReference
WeakReference也继承了Reference对象。
WeakReference与SoftReference
这两上类都继承了Reference对象,基本的操作都一样的。唯一的区别就是SoftReference内部的属性(private long timestamp; 在每次get的时候会更新该值),VM有可能要GC的时候使用该字段来判断。这就和两类引用的区别相关连了,WeakReference每次GC时就会直接回收该引用的对象,而SoftReference只有在内存不够用的时候才会回收对象,而回收哪一个对象,可能就需要这个字段来区分。
FinalReference
class FinalReference<T> extends Reference<T> { public FinalReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
这个类的权限是package,我们没法new出一个对象来使用。并且也只是继承了Reference类,没有别的特殊的操作。但同一个包有一类Finalizer,Finalizer继承了FinalReference,它也是一个package权限的类,同时是final的,不能被继承了。
//全局一个ReferenceQueue private static ReferenceQueue queue = new ReferenceQueue(); //全局只有一个unfinalized,也可以组成对象链 private static Finalizer unfinalized = null; private static final Object lock = new Object(); private Finalizer next = null, prev = null; // 将自己添加到unfinalized队列中 private void add() { synchronized (lock) { if (unfinalized != null) { this.next = unfinalized; unfinalized.prev = this; } unfinalized = this; } } //私有的构造方法 private Finalizer(Object finalizee) { //任何Finalizer对象的GC后都会到queue中 super(finalizee, queue); add(); }
/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
register执行的时候会new出对象,只有f类才会被JVM调用register。
f类:实现了finalize方法并且非空的类。类的加载过程就已经标记为是否f类。
Finalizer类最后有一些static的代码块:
private static class FinalizerThread extends Thread { private volatile boolean running; FinalizerThread(ThreadGroup g) { super(g, "Finalizer"); } public void run() { if (running) return; // Finalizer thread starts before System.initializeSystemClass // is called. Wait until JavaLangAccess is available while (!VM.isBooted()) { // delay until VM completes initialization try { VM.awaitBooted(); } catch (InterruptedException x) { // ignore and continue } } final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); running = true; for (;;) { try { //从ReferenceQueue中取出对象,执行对象的runFinalizer方法 Finalizer f = (Finalizer)queue.remove(); f.runFinalizer(jla); } catch (InterruptedException x) { // ignore and continue } } } } static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread finalizer = new FinalizerThread(tg); //线程优先级低 finalizer.setPriority(Thread.MAX_PRIORITY - 2); finalizer.setDaemon(true); finalizer.start(); }
runFinalize方法会通过JVM调用object的finalize方法
private boolean hasBeenFinalized() { return (next == this); } private void runFinalizer(JavaLangAccess jla) { synchronized (this) { //若next==this,则表明this对象已经从unfinalized对象链中移除,已经执行过一次runFinalizer了 if (hasBeenFinalized()) return; //将该对象从unfinalized对象链中移除 remove(); } try { Object finalizee = this.get(); if (finalizee != null && !(finalizee instanceof java.lang.Enum)) { //通过JDK调用对象的finalize方法 jla.invokeFinalize(finalizee); /* Clear stack slot containing this variable, to decrease the chances of false retention with a conservative GC */ finalizee = null; } } catch (Throwable x) { } super.clear(); }
1 当GC发生时,GC算法会判断f累对象是不是只被Finalizer类引用
2若这个累仅仅被Finalizer对象引用,说明这个对象在不就的将来会被回收,现在可以执行他的inalize方法了。
3 将这个队形放到Finalizer类的ReferenceQueue中,但这个f类对象其实并没有被回收,因为Finalizer这个类还对他们保持引用。
4 GC完成之前,JVM会调用ReferenceQueue中lock对象的notify方法,
5 Finalizer的守护线程可能会被唤醒,从Queue中取出对象(remove),执行该Finalizer对象的runFinalizer方法(1 将自己从unfinalized对象链中去除,2 执行引用对象的finalize方法)
6 下次GC时回收这个对象。
- f对象因为
Finalizer
的引用而变成了一个临时的强引用,即使没有其他的强引用,还是无法立即被回收; - f对象至少经历两次GC才能被回收,因为只有在
FinalizerThread
执行完了f对象的finalize
方法的情况下才有可能被下次GC回收,而有可能期间已经经历过多次GC了,但是一直还没执行f对象的finalize
方法; - CPU资源比较稀缺的情况下
FinalizerThread
线程有可能因为优先级比较低而延迟执行f对象的finalize
方法; - 因为f对象的
finalize
方法迟迟没有执行,有可能会导致大部分f对象进入到old分代,此时容易引发old分代的GC,甚至Full GC,GC暂停时间明显变长; - f对象的
finalize
方法被调用后,这个对象其实还并没有被回收,虽然可能在不久的将来会被回收。 - You can see preference processing times (and number of references) in GC logs with
-XX:+PrintReferenceGC
参考:
以上是关于java中的Reference的主要内容,如果未能解决你的问题,请参考以下文章