java中有四种类型的引用,关于引用的类在java.lang.ref包下,其类图如下:
各种引用类型介绍
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
示列代码如下:
public void test() { //MyDate finalRef = new MyDate(); MyDate softRef = new MyDate(); MyDate weakRef = new MyDate(); MyDate phantomRef = new MyDate(); ReferenceQueue<MyDate> softQueue = new ReferenceQueue<MyDate>(); ReferenceQueue<MyDate> weakQueue = new ReferenceQueue<MyDate>(); ReferenceQueue<MyDate> phantomQueue = new ReferenceQueue<MyDate>(); SoftReference<MyDate> soft = new SoftReference<MyDate>(softRef, softQueue); WeakReference<MyDate> weak = new WeakReference<MyDate>(weakRef, weakQueue); PhantomReference<MyDate> phantom = new PhantomReference<MyDate>(phantomRef, phantomQueue); softRef = null; weakRef = null; phantomRef = null; print(soft); print(weak); print(phantom); System.out.println("phantom.isEnqueued:"+phantom.isEnqueued()); System.gc(); System.out.println("============================================================="); print(soft); print(weak); print(phantom); //phantom.enqueue(); System.out.println("phantom.isEnqueued:"+phantom.isEnqueued()); System.gc(); System.gc(); System.gc(); System.gc(); System.gc(); System.out.println("============================================================="); print(phantom); System.out.println("phantom.isEnqueued:"+phantom.isEnqueued()); } public void print(Reference<MyDate> ref) { MyDate obj = ref.get(); System.out.println("ref = "+ref+"\tobj="+obj); }
打印结果如下:
ref = java.lang.ref.SoftReference@61de33 obj=Date: 1399972627546 ref = java.lang.ref.WeakReference@14318bb obj=Date: 1399972627546 ref = java.lang.ref.PhantomReference@ca0b6 obj=null phantom.isEnqueued:false ============================================================= ref = java.lang.ref.SoftReference@61de33 obj=Date: 1399972627546 ref = java.lang.ref.WeakReference@14318bb obj=null ref = java.lang.ref.PhantomReference@ca0b6 obj=null obj [Date: 1399972627546] is gc phantom.isEnqueued:false obj [Date: 1399972627546] is gc ============================================================= ref = java.lang.ref.PhantomReference@ca0b6 obj=null phantom.isEnqueued:true
多次GC之后,虚引用被加入到引用队列中
虚引用会引起OOM
public void test() { Reference<MyRef>[] referent = new PhantomReference[100000]; ReferenceQueue<MyRef> queue = new ReferenceQueue<MyRef>(); for (int i = 0; i < referent.length; i++) { referent[i] = new PhantomReference<MyRef>(new MyRef(), queue);// throw } System.out.println(referent[referent.length-1].get()); }
设置最大堆内存为2M,最后打印:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at test.ref.TestPhantom.test(TestPhantom.java:21) at test.ref.TestPhantom.main(TestPhantom.java:11)
引用类型总结
引用类型 | 获取引用对象方式 | 引用对象回收条件 | 是否会造成OOM |
强引用 | 直接获取 | 不回收 | 是 |
软引用 | 通过引用对象的get() | 内存满时 | 否 |
弱引用 | 通过引用对象的get() | 垃圾回收时 | 否 |
虚引用 | 无法获得 | 不回收 | 是 |
JDK中的后台线程:
java.lang.ref.Reference$ReferenceHandler
当对象被回收时,虚拟机触发这个引用线程,这个线程用来处理各种类型的引用,并将引用加入到引用
队列中
java.lang.ref.Finalizer$FinalizerThread
当检查到有强引用被加入到队列后,就从队列中取出引用并,之后调用finalize()方法。并将强引用队列的前后引用关系清空
引用队列和两个后台线程执行图如下:
在这个图中, 各种引用被放入到一个队列中,这是一个双向队列,由后台线ReferenceHandler负责处理这个队列,将不同的引用加入到相应的队列中,并将强引用加入到强引用队列中。
此时FinalizerThread检查到有引用加入到队列中了,就将其从最上方的引用队列中删除,然后调用Object#finalize()方法。
ReferenceHandler核心逻辑如下:
public void run() { for (;;) { Reference r; synchronized (lock) { if (pending != null) { r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { try { lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); } }
FinalizerThread核心逻辑和引用队列的逻辑:
public void run() { for (;;) { try { Finalizer f = (Finalizer)queue.remove(); f.runFinalizer(); } catch (InterruptedException x) { continue; } } } //引用队列会阻塞获取: 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; if (timeout != 0) return null; } } }
通过jstack打印出的线程堆栈:
最后还有一个WeakHashMap,它的键是弱引用类型,值为强引用,当键的引用被回收后,这个KV对就会被删除,WeakHashMap中的Entry,就将Key包装成WeakReference,将加入到弱引用队列中,
每次调用get都会对弱引用队列做检查,如果有数据则将其删除,其实现函数是expungeStaleEntries()
public void weak() { Map<MyRef, Object> weakmap = new WeakHashMap<MyRef, Object>(); MyRef a = new MyRef(); MyRef b = new MyRef(); weakmap.put(a, "aaa"); weakmap.put(b, "bbb"); weakmap.remove(a); a = null; b = null; System.gc(); Iterator<Entry<MyRef, Object>> i = weakmap.entrySet().iterator(); while (i.hasNext()) { Map.Entry<MyRef, Object> en = i.next(); System.out.println("map:" + en.getKey() + ":" + en.getValue()); } }
参考: