我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼
文章目錄
- 一、前言
- 二、架構
- 三、思考 Finalizer Vs Cleaner 放一起
- 四、Finalizer 源碼剖析
- 4.1 父類 FinalReference
- 4.2 Finalizer 類
- 4.3 Finalizer 類變量
- 4.4 對象初始化
- 4.5 add 方法
- 4.6 remove 方法
- 4.7 hasBeenFinalized 方法
- 4.8 static塊
- 4.9 內部類 FinalizerThread
- 4.10 runFinalizer 方法
- 4.11 clear() 方法
- 4.12 總結一下
- 五、JVM負責調用的三方法
- 六、番外篇
一、前言
建議提前閱讀:
- 【JAVA Reference】ReferenceQueue 與 Reference 源碼剖析(二)
- 【JAVA Reference】Cleaner 源碼剖析(三)
- 【JAVA Reference】Cleaner 對比 finalize 對比 AutoCloseable(四)
二、架構
2.1 代碼架構
- Finalizer 繼承 FinalReference 再繼承 Reference。
2.2 UML流程圖
三、思考 Finalizer Vs Cleaner 放一起
- 我們知道重寫 finalize 方法與定義一個 cleaner 都可以實現在gc回收前進行一系列操作。建議先看 【JAVA Reference】Cleaner 對比 finalize 對比 AutoCloseable(四)
3.1 代碼 demo
public class _05_03_TestCleanerWithFinalize {
public static void main(String[] args) throws Exception {
int index = 0;
while (true) {
Thread.sleep(1000);
// 提醒 GC 去進行垃圾收集了
System.gc();
// 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
DemoObject obj = new DemoObject("demo" + index++);
Cleaner.create(obj, new CleanerTask("thread_" + index++));
}
}
@Data
@AllArgsConstructor
@ToString
static class DemoObject {
private String name;
@Override
protected void finalize() throws Throwable {
System.out.println("finalize running DoSomething ..." + name);
}
}
static class CleanerTask implements Runnable {
private String name;
public CleanerTask(String name) {
this.name = name;
}
// do something before gc
@Override
public void run() {
System.out.println("CleanerTask running DoSomething ..." + name );
}
}
}
輸出:
finalize running DoSomething ...demo0
CleanerTask running DoSomething ...thread_1
finalize running DoSomething ...demo2
CleanerTask running DoSomething ...thread_3
finalize running DoSomething ...demo4
CleanerTask running DoSomething ...thread_5
finalize running DoSomething ...demo6
CleanerTask running DoSomething ...thread_7
finalize running DoSomething ...demo8
...
可以看到,每次 finalize 總是比 Cleaner 先執行,不管你run幾次,結果都一樣,那麼思考一下爲什麼?
3.2 爲什麼 finalize 總是比 Cleaner 先執行 ?
- 結論先拋出來: Cleaner 和 finalize 內部都有指針Pointer 指向了即將要回收的 Object 對象,但是 Cleaner 底層是虛引用(PhamtonReference),而 finalize的底層是 Finalizer ,屬於強引用。所以,必須強引用的釋放完對象,才輪到 Cleaner。
四、Finalizer 源碼剖析
4.1 父類 FinalReference
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
- 注意:這個類的修飾符是 default ,也就是隻能在包 java.lang.ref 內被使用 。
- 由繼承關係可以得知,FinalReference 只有一個子類 Finalizer
4.2 Finalizer 類
final class Finalizer extends FinalReference<Object>
- 注意,Finalizer 是個 final 類,也就是斷子絕孫類,不會有繼承,可以防止篡改與注入。
- Finalizer 類的修飾符是 default ,也就是隻能在包 java.lang.ref 內被使用 。那麼總覽了一下,他是 JVM 調用的。
4.3 Finalizer 類變量
4.3.1 私有static變量 queue
Finalizer引用的對象被gc之前,jvm會把相應的Finalizer對象放入隊列 queue
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
=== 關於Reference的四個狀態,可以看圖 ===
=== 點擊查看top目錄 ===
4.3.2 私有static變量 unfinalized
靜態的Finalizer對象鏈,每=== 實例化 ===一個對象,這個隊列就會插入=== add ===一個。
// 靜態的Finalizer對象鏈
private static Finalizer unfinalized = null;
4.3.3 next + prev 指針
// 雙端指針
private Finalizer
next = null,
prev = null;
4.3.3 私有 static final 變量 lock
private static final Object lock = new Object();
- 每次對 unfinalized 隊列進行 add 與 remove 的時候,都會進行加鎖。
4.4 對象初始化
4.4.1 構造方法
- 私有構造方法,說明無法通過 new 實例化
private Finalizer(Object finalizee) {
// 4.4.1 初始化
super(finalizee, queue);
// 4.5 往前面插入
add();
}
- 看下 super 構造函數
傳入兩個參數: referent 與 ReferenceQueue
有了 ReferenceQueue 說明可以從pending 進入到 Enqueue 隊列。
=== 假如未註冊 ReferenceQueue,那麼不會進入 Enqueue 隊列 ===
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
4.4.2 真正初始化對象的 register 方法
- java.lang.ref.Finalizer#register
/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
- 註冊 Finalizer 對象,只要是類 override Finalize 方法的,初始化類的對象後,都會被VM註冊到這裏來。
- 參數 Object finalizee ,表示指針指向的對象。
4.5 add 方法
往隊列 unfinalized 的頭部插入
// 往頭部插
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
4.6 remove 方法
往隊列 unfinalized 的頭部移除
// 從頭部移除
private void remove() {
synchronized (lock) {
if (unfinalized == this) {
if (this.next != null) {
unfinalized = this.next;
} else {
unfinalized = this.prev;
}
}
if (this.next != null) {
this.next.prev = this.prev;
}
if (this.prev != null) {
this.prev.next = this.next;
}
// next 和 prev 都指向自己,表明已經被 remove 掉了,準備調用類重新複寫的 finalize 方法
this.next = this; /* Indicates that this has been finalized */
this.prev = this;
}
}
4.7 hasBeenFinalized 方法
- 是否已經執行了 finalize 方法,如果 this.next = this; this.prev = this; 那麼就是說明已經執行了。 因爲 runFinalizer 的內部調用了 remove 方法
private boolean hasBeenFinalized() {
return (next == this);
}
4.8 static塊
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); // 特地讓出了優先級,這個跟cleaner還不一樣
finalizer.setDaemon(true); // 幽靈線程,這個跟 cleaner 一樣
finalizer.start(); // 把線程給跑起來
}
- 啓動一個內部類 FinalizerThread 線程。
- 設置優先級是 Thread.MAX_PRIORITY - 2,Cleaner 的優先級是 MAX_PRIORITY。雖然Cleaner的優先級比 FinalizerThread 高,但是由於 FinalizerThread 有強引用,所以 Cleaner 還是比較晚地執行。
- setDaemon 幽靈線程,這個跟 cleaner 一樣
-
這個靜態代碼塊跟 === Reference的static代碼塊 ReferenceHandler=== 很相近 。
-
接下來看下內部類 FinalizerThread 是幹什麼的!!!
=== 點擊查看top目錄 ===
4.9 內部類 FinalizerThread
- java.lang.ref.Finalizer.FinalizerThread 私有靜態內部類
private static class FinalizerThread extends Thread {
// 這個參數用來判斷該線程是否已經啓動
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
// in case of recursive call to run()
// 如果發生了遞歸調用則直接返回
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
// Finalizer線程在 System.initializeSystemClass 被調用前啓動
// 需要等到JVM已經初始化完成才能執行
// 4.9.1
while (!VM.isBooted()) {
// delay until VM completes initialization
// 延遲等待 JVM 完全初始化完畢
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
// 4.9.2
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); // getter 直接看 Reference 類的 static 代碼塊
running = true;
// 4.9.3
for (;;) {
try {
// 將 Finalizer 從 queue 隊列中拿出來然後調用完方法後釋放
// Active -> pending -> enqueue -> 出隊列,進行處理
Finalizer f = (Finalizer)queue.remove();
// 調用其runFinalizer方法,實質是要去調用該對象覆寫的 finalize 方法
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
// 這個地方要注意,你重寫的 finalize 方法,裏面拋出的異常,是直接忽略的
}
}
}
}
4.9.1 VM.isBooted() 方法
判斷 JVM 是否已經啓動,如果還沒啓動,那麼 VM.awaitBooted() 等待 JVM 啓動再說。
4.9.2 SharedSecrets.getJavaLangAccess();
- setter 在 java.lang.System#setJavaLangAccess 方法內部
4.9.3 死循環執行 runFinalizer
for (;;) {
try {
// 將 Finalizer 從 queue 隊列中拿出來然後調用完方法後釋放
// Active -> pending -> enqueue -> 出隊列,進行處理
Finalizer f = (Finalizer)queue.remove();
// 調用其runFinalizer方法,實質是要去調用該對象覆寫的 finalize 方法
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
// 這個地方要注意,你重寫的 finalize 方法,裏面拋出的異常,是直接忽略的
}
}
- queue.remove(); 把 queue 中的 Referece 全部拿出來,然後調用他們override的finalize方法。
4.9.3.1 注意一下 try-catch 喫掉異常
- 這個地方直接就把異常喫掉了,那麼類定義的 finalize方法不管出現什麼情況,都不會有異常打印出來,也不會對異常做任何處理。(即使你調用了 10 / 0),=== 具體看代碼Demo#2.1.2 ===
4.10 runFinalizer 方法
- java.lang.ref.Finalizer#runFinalizer
// 執行 finalize 方法,然後進行GC回收
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
// 只能執行1次,如果已經執行了。那麼直接 return 回收
if (hasBeenFinalized()) return;
//執行 remove方法 ,從 unfinalized 鏈中拿掉該節點,然後返回出來,有點類似於 pop 操作
remove();
}
try {
// 拿到 指針指向的對象
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
// 對象調用 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) { }
// 4.9.5 解除強引用
super.clear();
}
- 先判斷是否已經執行過 finalize 方法了,是的話,就返回。所以記住,finalize方法只會執行一次。
- 調用 remove() 方法,把 next 和 prev都指向自己,所以剛剛方法hasBeenFinalized判斷是否已經處理過的依據,就是這麼來的。
- Object finalizee = this.get(); 記住了,這個是個強引用,因爲都是放在unfinalized隊列裏面的。
- jla.invokeFinalize(finalizee); 調用對象 finalizee 的 finalize方法。
- 局部變量 finalizee = null,指針 finalizee 指向了空,方便 GC
4.11 clear() 方法
- java.lang.ref.Reference#clear
public void clear() {
this.referent = null;
}
解除強引用。
4.12 總結一下
JVM啓動時,自動開啓兩個線程
- Finalizer 內部 static 塊啓動了FinalizerThread 線程會把 reference 從 queue 隊列中再拿出來,調用他的 finalize 方法,然後再去回收。
- 初始化 Finalizer 的父類 Reference 的 static 塊啓動了 ReferenceHandler 線程會不斷把 pending 隊列的 reference 丟到 queue 隊列,
=== 關於Reference的流程圖 ===
五、JVM負責調用的三方法
5.1 Register方法
- 只要發現有哪個類複寫了finalize方法,那麼就會進行 register
5.2 runFinalization 方法
5.2.1 Runtime#runFinalization 方法
- java.lang.Runtime#runFinalization 系統關閉時會調用
/* Wormhole for calling java.lang.ref.Finalizer.runFinalization */
private static native void runFinalization0();
public void runFinalization() {
runFinalization0();
}
5.2.2 Finalizer.runFinalization 方法
/* Called by Runtime.runFinalization() */
static void runFinalization() {
// JVM 是否啓動
if (!VM.isBooted()) {
return;
}
forkSecondaryFinalizer(new Runnable() {
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 (;;) {
Finalizer f = (Finalizer)queue.poll();
if (f == null) break;
f.runFinalizer(jla);
}
}
});
}
- 把 referenceQueue 裏面的 Reference 都拿出來,然後調用 finalize 方法
5.2.3 forkSecondaryFinalizer 方法
- 調用了AccessController.doPrivileged方法,這個方法的作用是使其內部的代碼段獲得更大的權限,可以在裏面訪問更多的資源。
private static void forkSecondaryFinalizer(final Runnable proc) {
// 調用了AccessController.doPrivileged方法,這個方法的作用是使其內部的代碼段獲得更大的權限,可以在裏面訪問更多的資源。
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread sft = new Thread(tg, proc, "Secondary finalizer");
sft.start();
try {
sft.join();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
return null;
}});
}
5.3 runAllFinalizers 方法
5.3.1 Shutdown#runAllFinalizers 方法
- java.lang.Shutdown#runAllFinalizers
/* Wormhole for invoking java.lang.ref.Finalizer.runAllFinalizers */
private static native void runAllFinalizers();
5.3.2 Finalizer.runAllFinalizers 方法
/* Invoked by java.lang.Shutdown */
static void runAllFinalizers() {
// JVM 是否啓動
if (!VM.isBooted()) {
return;
}
forkSecondaryFinalizer(new Runnable() {
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 (;;) {
Finalizer f;
synchronized (lock) {
f = unfinalized;
if (f == null) break;
unfinalized = f.next;
}
f.runFinalizer(jla);
}}});
}
- 把 unfinalized 裏面的 Reference 都拿出來,然後調用 finalize 方法
- 這裏加了一把鎖,因爲有進有出
5.4 總結一下
5.4.1 runAllFinalizers 與 runFinalization 的不同
/* Called by Runtime.runFinalization() */
static void runFinalization() {
...
for (;;) {
Finalizer f = (Finalizer)queue.poll();
if (f == null) break;
f.runFinalizer(jla);
}
...
}
/* Invoked by java.lang.Shutdown */
static void runAllFinalizers() {
...
for (;;) {
Finalizer f;
synchronized (lock) {
f = unfinalized;
if (f == null) break;
unfinalized = f.next;
}
f.runFinalizer(jla);
}
...
}
- 前者從 referenceQueue 裏面拿 reference,也就是把 queue 隊列消耗光。後者從 unfinalized 隊列拿 reference,說明是要把全部已經實例化的對象(覆蓋了finalize方法的類的對象)拿出來。
- 注意 unfinalized 隊列裏面的 Reference 有可能還沒有被 GC,只是登記在冊而已,而queue隊列裏面的 reference 都是GC丟進來的了。
- Shutdown 打算關閉 JVM了,所以把暫時還沒被GC的也一起跑到最後,就像是網吧今天停止營業了,不管你上網多久(剛上網5分鐘的還有上網2小時快沒錢了準備走人的),都得結賬走人。