概述
Java的引用分為StrongReference杉编、SoftReference、WeakReference、PhantomReference和FinalReference;
其中StrongRference并沒有定義成接口忱详,而其余四種都有相應的接口定義;
這五種引用的使用場景如下:
- 強引用
最普遍的引用跺涤。如果一個對象具有強引用匈睁,垃圾回收器絕不會回收它。當內(nèi)存空 間不足桶错,Java虛擬機寧愿拋出OutOfMemoryError錯誤航唆,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足問題院刁。 - 軟引用
如果內(nèi)存空間足糯钙,則不回收;如果內(nèi)存空間不足退腥,則會被回收任岸;通常可以用來做為緩存狡刘; - 弱引用
一旦發(fā)生GC,弱引用對象立即被回收演闭; - 虛引用
虛引用不會影響對象的生命周期。如果一個對象僅持有虛引用颓帝,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收窝革;
SoftReference和WeakReference
上面提到SoftReference適合用來實現(xiàn)緩存购城,例如google的guava cache就使用到了WeakReference和SoftReference,在創(chuàng)建cache時可以通過如下方式指定用哪種引用:
//使用WeakReference
CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES).weakKeys().weakValues()
.build();
//使用SoftReference
CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES).softValues()
.build();
創(chuàng)建SoftReference和WeakReference的構(gòu)造函數(shù)如下:
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
可以看到可以傳入一個ReferenceQueue對象虐译,那么這個傳入到Queue對象到底有什么用呢瘪板?如果WeakReference所引用的對象被垃圾回收,Java虛擬機就會把這個WeakReference加入到與之關(guān)聯(lián)的Queue中漆诽;
PhantomReference
DirectByteBuffer是JDK提供的堆外內(nèi)存對象侮攀,其構(gòu)造函數(shù)如下:
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
其中的Cleaner對象繼承自PhantomReference:
public class Cleaner extends PhantomReference<Object>
那么定義成PhantomReference起什么作用呢锣枝?其實主要是為了回收堆外內(nèi)存,關(guān)于其具體的實現(xiàn)兰英,后面會進行介紹撇叁;
FinalReference
FinalReference有個子類Finalizer,這兩個類的包訪問級別都是default而不是public的畦贸,因此我們無法在代碼中直接調(diào)用陨闹;
說到FinalReference的使用,就免不了談及對象的創(chuàng)建:
instanceOop InstanceKlass::allocate_instance(TRAPS) {
bool has_finalizer_flag = has_finalizer(); //判斷當前類是否包含非空的finalize方法
int size = size_helper(); //確定要分配的內(nèi)存大小
KlassHandle h_k(THREAD, this);
instanceOop i;
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
if (TraceFinalizerRegistration) {
tty->print("Registered ");
i->print_value_on(tty);
tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", (address)i);
}
instanceHandle h_i(THREAD, i);
// Pass the handle as argument, JavaCalls::call expects oop as jobjects
JavaValue result(T_VOID);
JavaCallArguments args(h_i);
methodHandle mh (THREAD, Universe::finalizer_register_method());
JavaCalls::call(&result, mh, &args, CHECK_NULL);
return h_i();
}
可以看到對象創(chuàng)建時薄坏,如果發(fā)現(xiàn)對象實現(xiàn)了finalize方法趋厉,則會調(diào)用Finalizer.register方法,將對象注冊到Finalizer的靜態(tài)屬性unfinalized上胶坠,形成Finalizer鏈表君账;
final class Finalizer extends FinalReference<Object> {
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private static Finalizer unfinalized = null;
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 {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
}
可以看到FinalizerThread線程會從queue隊列中獲取Finalizer對象,調(diào)用其finalize方法沈善;queue即創(chuàng)建Reference時傳入的queue對象乡数;
那么,F(xiàn)inalizer什么時候會加入到queue隊列呢矮瘟?很容易想到瞳脓,當GC發(fā)生時,F(xiàn)inalizer對象會加入到queue隊列澈侠;
那么JVM是如何實現(xiàn)當對象回收時劫侧,將對象添加到queue呢?
# Reference
Reference對象是上述對象的基類哨啃,它的結(jié)構(gòu)如下:
private T referent;
volatile ReferenceQueue<? super T> queue;//對象被回收后烧栋,Reference會被放入隊列
Reference next;
transient private Reference<T> discovered; /* used by VM */
private static Reference<Object> pending = null;//JVM GC時,會將Reference對象設(shè)置到pending上拳球,discovered相當于next,指向下一個Reference對象
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
c = r instanceof Cleaner ? (Cleaner) r : null;
pending = r.discovered;
r.discovered = null;
} else {
if (waitForNotify) {//如果等待审姓,則調(diào)用lock.wait方法,當GC時將Reference添加到pending隊列時祝峻,會調(diào)用lock.notify方法魔吐,
lock.wait();
}
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
Thread.yield();
return true;
} catch (InterruptedException x) {
return true;
}
if (c != null) {//如果是Cleaner對象,調(diào)用其clean方法釋放資源
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
前面提到的DirectByteBuffer內(nèi)存的釋放就是通過JVM GC時將對象添加到pending對象莱找,而Reference中的ReferenceHandler線程會將pending中的對象添加到ReferenceQueue,同時如果發(fā)現(xiàn)是Cleaner對象酬姆,會調(diào)用clean方法釋放堆外內(nèi)存;
對于Finalizer對象奥溺,當被添加到ReferenceQueue時辞色,會通過FinalizerThread線程調(diào)用finalize方法;因此可以看到Finalizer對象至少要經(jīng)歷兩次垃圾回收才能被回收浮定,同時由于FinalizerThread線程的優(yōu)先級為Thread.MAX_PRIORITY - 2相满,因此當系統(tǒng)壓力較大時层亿,F(xiàn)inalizer對象可能要很久才能被回收;