我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼
一、架構
二、概念
- sun.misc.Cleaner是JDK內部提供的用來釋放非堆內存資源的API。JVM只會幫我們自動釋放堆內存資源,但是堆外內存無能爲力,該類提供了回調機制,通過這個類能方便的釋放系統的其他資源。
- 目的就是在 GC 前,你還可以做點事情。這是你最後的機會
- Cleaner 繼承了 PhantomReference,是一個虛幻引用。
三、實戰 demo
public class _05_00_TestCleaner {
public static void main(String[] args) throws Exception {
int index = 0;
while (true) {
Thread.sleep(1000);
// 提醒 GC 去進行垃圾收集了
System.gc();
// 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
DemoObject obj = new DemoObject("demo01");
/*
增加 obj 的虛引用,定義清理的接口 DoSomethingThread
第一個參數:需要監控的堆內存對象
第二個參數:程序釋放資源前的回調。
*/
Cleaner.create(obj, new DoSomethingThread("thread_" + index++));
}
}
static class DoSomethingThread implements Runnable {
private String name;
public DoSomethingThread(String name) {
this.name = name;
}
// do something before gc
@Override
public void run() {
System.out.println(name + " running DoSomething ...");
}
}
@Data
@AllArgsConstructor
static class DemoObject{
private String name;
}
}
輸出:
thread_0 running DoSomething ...
thread_1 running DoSomething ...
thread_2 running DoSomething ...
thread_3 running DoSomething ...
thread_4 running DoSomething ...
thread_5 running DoSomething ...
thread_6 running DoSomething ...
thread_7 running DoSomething ...
thread_8 running DoSomething ...
thread_9 running DoSomething ...
...
結論:
cleaner 類,可以讓你在對象被回收前,乾點其他事情。臨終遺言吧。效果等同於 finalize
四、源碼剖析
4.1 sun.misc.Cleaner 類
package sun.misc;
import java.lang.ref.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
public class Cleaner
extends PhantomReference<Object>
{
// 靜態私有全局變量
// 在 ReferenceHandler 線程的處理中, Cleaner 對象是不進入這個隊列的,設置的目的,是爲了能進 pending
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
// Doubly-linked list of live cleaners, which prevents the cleaners
// themselves from being GC'd before their referents
//
// 靜態私有全局變量,鏈表的頭
static private Cleaner first = null;
private Cleaner
next = null,
prev = null;
// 往前面插入
private static synchronized Cleaner add(Cleaner cl) {
if (first != null) {
cl.next = first;
first.prev = cl;
}
first = cl;
return cl;
}
// 刪除某個節點
private static synchronized boolean remove(Cleaner cl) {
// If already removed, do nothing
// 已經被刪除了,就啥事都不用幹
if (cl.next == cl)
return false;
// 如果是頭節點
if (first == cl) {
// 如果有後繼節點
if (cl.next != null)
first = cl.next;
else // 如果是頭節點,又沒後繼節點,那麼 first = null
first = cl.prev;
}
// 如果後繼節點不是 null
if (cl.next != null)
cl.next.prev = cl.prev;
// 如果前驅節點不是 null
if (cl.prev != null)
cl.prev.next = cl.next;
// next 和 prev 指針都指向自己,這個表示已經是被刪掉了
// Indicate removal by pointing the cleaner to itself
cl.next = cl;
cl.prev = cl;
return true;
}
//實現 Runable 接口的對象,這個對象會在實現的 run 方法裏做 gc 前清理資源的操作,它的run方法最終會由 ReferenceHander 線程來調用執行
// ReferenceHander 從 pending 隊列裏面取數據,然後調用 sun.misc.Cleaner#clean 方法,clean 方法會調用 thunk.run() 方法
private final Runnable thunk;
//私有的構造方法,說明 Cleaner 對象是無法直接被創建的,參數爲被引用的對象和 ReferenceQueue 成員變量
// 創建方法爲下方的 create
private Cleaner(Object referent, Runnable thunk) {
super(referent, dummyQueue);
this.thunk = thunk;
}
//這個create靜態方法提供給我們來實例化Cleaner對象,需要兩個參數,被引用的對象與實現了Runnable接口的對象,新創建的Cleaner對象被加入到了 dummyQueue 隊列裏
public static Cleaner create(Object ob, Runnable thunk) {
if (thunk == null)
return null;
return add(new Cleaner(ob, thunk));
}
/**
* Runs this cleaner, if it has not been run before.
*/
// clean方法先將對象從 dummyQueue 隊列remove移除(這樣 Cleaner 對象就可以被gc回收掉了),然後調用thunk的run方法後執行清理操作
public void clean() {
if (!remove(this))
return;
try {
thunk.run();
} catch (final Throwable x) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null)
new Error("Cleaner terminated abnormally", x)
.printStackTrace();
System.exit(1);
return null;
}});
}
}
}
4.2 構造方法
private Cleaner(Object referent, Runnable thunk) {
super(referent, dummyQueue);
this.thunk = thunk;
}
私有的構造方法,說明 Cleaner 對象是無法直接被創建的,參數爲被引用的對象和 ReferenceQueue 成員變量,真正創建方法爲下方的 create。
=== 點擊查看top目錄 ===
4.3 create 方法
這個create靜態方法提供給我們來實例化Cleaner對象,需要兩個參數:
- 被引用的對象
- 實現了Runnable接口的對象,這個用來回調的時候執行內部的 run 方法
新創建的Cleaner對象被加入到了 dummyQueue 隊列裏。
public static Cleaner create(Object ob, Runnable thunk) {
if (thunk == null)
return null;
return add(new Cleaner(ob, thunk));
}
4.4 add 方法
// 往前面插入 ,跟 Reference 類的隊列一脈相承
private static synchronized Cleaner add(Cleaner cl) {
if (first != null) {
cl.next = first;
first.prev = cl;
}
first = cl;
return cl;
}
4.5 remove 方法
// 刪除某個節點
private static synchronized boolean remove(Cleaner cl) {
// If already removed, do nothing
// 已經被刪除了,就啥事都不用幹
if (cl.next == cl)
return false;
// 如果是頭節點
if (first == cl) {
// 如果有後繼節點
if (cl.next != null)
first = cl.next;
else // 如果是頭節點,又沒後繼節點,那麼 first = null
first = cl.prev;
}
// 如果後繼節點不是 null
if (cl.next != null)
cl.next.prev = cl.prev;
// 如果前驅節點不是 null
if (cl.prev != null)
cl.prev.next = cl.next;
// next 和 prev 指針都指向自己,這個表示已經是被刪掉了
// Indicate removal by pointing the cleaner to itself
cl.next = cl;
cl.prev = cl;
return true;
}
4.6 clean 方法
clean方法先將對象從 dummyQueue 隊列remove移除(這樣 Cleaner 對象就可以被gc回收掉了),然後調用thunk的run方法後執行清理操作。
// clean方法先將對象從 dummyQueue 隊列remove移除(這樣 Cleaner 對象就可以被gc回收掉了),然後調用thunk的run方法後執行清理操作
public void clean() {
if (!remove(this))
return;
try {
thunk.run();
} catch (final Throwable x) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null)
new Error("Cleaner terminated abnormally", x)
.printStackTrace();
System.exit(1);
return null;
}});
}
}
該 clean 方法的調用,直接看上一篇【JAVA Reference】ReferenceQueue 與 Reference 源碼剖析(二)
4.7 static變量 dummyQueue
靜態私有全局變量
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
在 ReferenceHandler 線程的處理中, Cleaner 對象是不進入這個隊列的,設置的目的,是爲了能進 pending
=== 點擊查看top目錄 ===
4.8 static 變量 first
// 靜態私有全局變量,鏈表的頭,把一堆 cleaner 串起來
private static Cleaner first = null;
4.9 成員變量 thunk
private final Runnable thunk;
- 實現 Runable 接口的對象,這個對象會在實現的 run 方法裏做 gc 前清理資源的操作,它的run方法最終會由 ReferenceHander 線程來調用執行
- ReferenceHander 從 pending 隊列裏面取數據,然後調用 sun.misc.Cleaner#clean 方法,clean 方法會調用 thunk.run() 方法
具體看方法=== clean ===
五、番外篇
下一章節:【JAVA Reference】Cleaner 對比 finalize 對比 AutoCloseable(四)
上一章節:【JAVA Reference】ReferenceQueue 與 Reference 源碼剖析(二)