你們都知道java裏面引用有SoftReference、WeakReference、PhantomReference,他們都繼承自抽象類Reference,咱們看一下他的類圖: java
能夠發現,除了最熟悉的強引用沒有對應的Reference實現外,虛引用,弱引用和軟引用都有對應的Reference實現類。那麼,多出來的FinalReference實現是幹什麼的呢? bash
能夠看到,FinalReference類僅僅是繼承了Reference類而已。jvm
/**
* Final references, used to implement finalization
*/
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
複製代碼
註釋中說他是用來實現finalization(終結)的。post
其真正的邏輯位於FinalReference的惟一子類:java.lang.ref.Finalizer
中。ui
注意,該類爲包級私有,有final關鍵字修飾,且構造方法爲private,提供了register方法供JVM調用。this
final class Finalizer extends FinalReference<Object> {
//...
private Finalizer(Object finalizee) {
super(finalizee, queue);
add();
}
/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
}
複製代碼
由於register方法並無返回值,因此在外部是沒法獲取到建立的Finalizer對象。 其中,構造方法中調用的super(finalizee, queue)
會將入參finalizee加入到引用隊列queue
中。spa
關於引用隊列,見juejin.im/post/5e19d6…線程
咱們分析的轉入構造方法中所調用的add方法。code
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private static Finalizer unfinalized = null;
private static final Object lock = new Object();
private Finalizer
next = null,
prev = null;
//...
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
複製代碼
結合next
,prev
屬性和add
方法,能夠比較容易的看出unfinalized
其實是一個雙向鏈表,在add
方法被調用後,就會將當前對象加入到unfinalized
鏈表。cdn
其實,在構造方法
方法被調用後,實際上作了以下兩件事:
unfinalized
鏈表。由於register
方法並無返回值,且unfinalized
屬性爲靜態成員變量,因此當前建立對象在虛擬機內僅該unfinalized
鏈表持有一份引用
根據註釋和訪問規則來看,register
方法僅會被虛擬機所調用,並且,只有重寫了java.lang.Object#finalize
方法的類纔會被做爲參數調用Finalizer#register
方法。
與pending handler相似,在FinalReference中一樣也是使用靜態代碼塊來啓動後臺線程。
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();
}
複製代碼
看一下FinalizerThread
類,該類繼承了Thread類,並重寫run
方法。
private static class FinalizerThread extends Thread {
private volatile boolean running;
public void run() {
// in case of recursive call to run()
if (running)
return;
//...
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
複製代碼
上面代碼段中僅保留了關鍵流程代碼。
能夠看出在run方法內使用了一個死循環,每次循環先將隊首元素從引用隊列中取出(在構造方法內將對象註冊至引用隊列,當引用狀態變爲pending時,會由Pending-handler-thread將其加入該註冊隊列),並執行runFinalizer
方法。
繼續看runFinalizer
方法:
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
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();
}
複製代碼
根據方法名就能夠看出來,該方法的做用就是執行jla.invokeFinalize(finalizee)
,並執行一些清理操做。
而在JavaLangAccess
的實現類中(java.lang.System中的一個匿名內部類),invokeFinalize
的代碼也很是簡單,只是調用finalize
方法。
public void invokeFinalize(Object o) throws Throwable {
o.finalize();
}
複製代碼
在invokeFinalize
以後,代碼中去主動將finalizee設置爲null,根據上面的註釋可知,是爲了清除該方法對當前對象的引用,減少影響gc的機率。
在執行finalizee方法時,該對象會被臨時加一個強引用,進而對gc產生影響
從上面的分析過程能夠看出java.lang.Object
中的finalize
方法在對象將要被回收的時由一個守護線程去調用他們的finalize
方法。
因爲該線程的優先級並不能保證,在準備調用finalize
方法到調用結束時,可能已經通過了屢次gc,而因爲臨時的強引用,致使該對象遲遲沒有被回收。
可是,finalize
的調用並不能被保證。因此,該方法在java9已被標記爲過期。咱們也不該該去重寫該方法去作清理工做。
其實FinalReference就是jdk爲了將Finalizer方法實現相似析構方法而打造的類。
由虛擬機先將重寫了Finalizer方法的對象註冊至引用隊列,暫存在鏈表中。
當對象引用狀態變爲Enqueued後,由守護線程從引用隊列中取出對象,創建臨時的強引用,並調用Finalizer方法。
因爲守護線程的優先級較低,並不能保證重寫的Finalizer方法在被回收前必定會被執行。而且由於有臨時強引用的存在,還可能使該對象錯過gc。
因此,並不該該使用Finalizer方法~
jdk8源碼&doc