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());
}
}
参考: