1. 程式人生 > >第三講 談談final、finally、finalize有什麼不同?

第三講 談談final、finally、finalize有什麼不同?

final 可以用來修飾類、方法、變數,分別有不同的意義,nal 修飾的 class 代表不可以繼承擴充套件,final 的變數是不可以修改的,而 final 的方法也是不可以重寫的(override)。 finally 則是 Java 保證重點程式碼一定要被執行的一種機制。我們可以使用 try-finally 或者try-catch-finally 來進行類似關閉 JDBC 連線、保證 unlock 鎖等動作。 finalize 是基礎類 java.lang.Object的一個方法,它的設計目的是保證物件在被垃圾收集前完成特定資源的回收。finalize 機制現在已經不推薦使用,並且在 JDK 9 開始被標記為 deprecated。

final: 1、我們可以將方法或者類宣告為 final,,這樣就可以明確告知別人,這些行為是不許修改的。 2、使用 final 修飾引數或者變數,也可以清楚地避免意外賦值導致的程式設計錯誤,甚至,有人明確推薦將所有方法引數、本地變數、成員變數宣告成 final。 3、final 變數產生了某種程度的不可變(immutable)的效果,所以,可以用於保護只讀資料,尤其是在併發程式設計中,因為明確地不能再賦值 final 變數,有利於減少額外的同步開銷,也可以省去一些防禦性拷貝的必要。

finally: 1、是 Java 保證重點程式碼一定要被執行的一種機制。 2、我們可以使用try-catch-finally 來進行類似關閉JDBC 連線、保證 unlock 鎖等動作。

finalize 1、是基礎類 java.lang.Object 的一個方法,它的設計目的是保證物件在被垃圾收集前完成特定資源的回收。finalize 機制現在已經不推薦使用,並且在 JDK 9開始被標記為 deprecated。

只是擴充套件

1、注意:final不是immutable!
final List strList = new ArrayList<>();
strList.add("Hello");
strList.add("world");
List unmodifiableStrList == List.of("hello", "world");
unmodifiableStrList.add("again");

final 只能約束 strList 這個引用不可以被賦值,但是 strList 物件行為不被 final 影響,新增元素等操作是完全正常的。如果我們真的希望物件本身是不可變的,那麼需要相應的類支援不可變的行為。在上面這個例子中,List.of 方法建立的本身就是不可變 List,最後那句add 是會在執行時丟擲異常的。 Immutable 在很多場景是非常棒的選擇,某種意義上說,Java 語言目前並沒有原生的不可變支援,如果要實現immutable 的類,我們需要做到: 1)、將 class 自身宣告為 final,這樣別人就不能擴充套件來來繞過限制了。 2)、將所有成員變數定義為 private 和 final,並且不要實現 setter 方法。 3)、通常構造物件時,成員變數使用深度拷貝來初始化,而不是直接賦值,這是一種防禦措施,因為你無法確定輸入物件不被其他人修改。

2.finalize 真的那麼不堪? finalize 的執行是和垃圾收集關聯在一起的,一旦實現了非空的 finalize 方法,就會導致相應物件回收呈現數量級上的變慢,有人專門做過 benchmark,大概是 40~50 倍的的下降。 因為,finalize 被設計成在物件被垃圾收集前呼叫,,這就意味著實現了 finalize 方法的物件是個“特殊公民”,JVM 要對它進行額外處理。finalize 本質上成為了快速回收的阻礙者,可能導致你的物件經過多個垃圾收集週期才能被回收。 有人也許會問,我用 System.runFinalization​() 告訴 JVM 積極一點,是不是就可以了?也許有點用,但是問題在於,這還是不可預測、不能保證的,所以本質上還是不能指望。。實踐中,因為 finalize 拖慢垃圾收集,導致大量物件堆積,也是一種典型的導致 OOM 的原因。 從另一個角度,我們要確保回收資源就是因為資源都是有限的,垃圾收集時間的不可預測,可能會極大加劇資源佔用。這意味著對於消耗非常高頻的資源,千萬不要指望finalize 去承擔資源釋放的主要職責,最多讓 finalize 作為最後的“守門員”,況且它已經暴露了如此多的問題。這也是為什麼我推薦,資源用完即顯式釋放,或者利用資源池來儘量重用。 finalize 還會掩蓋資源回收時的出錯資訊,我們看下面一段 JDK 的原始碼,擷取自java.lang.ref.Finalizer

private void runFinalizer(JavaLangAccess jla) {
	// ... 省略部分程式碼
	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();
}

是的,你沒有看錯,這裡的Throwable 是被生吞了的!也就意味著一旦出現異常或者出錯,你得不到任何有效資訊。況且,,Java 在 finalize 階段也沒有好的方式處理任何資訊,不然更加不可預測。

  1. 有什麼機制可以替換 finalize 嗎? Java 平臺目前在逐步使用 java.lang.ref.Cleaner 來替換掉原有的finalize 實現。Cleaner的實現利用了幻象引用(PhantomReference),這是一種常見的所謂 post-mortem 清理機制。我會在後面的專欄系統介紹 Java 的各種引用,利用幻象引用和引用佇列,我們可以保證物件被徹底銷燬前做一些類似資源回收的工作,比如關閉檔案描述符(作業系統有限的資源),它比 finalize 更加輕量、更加可靠。 吸取了 finalize 裡的教訓,每個 Cleaner的操作都是獨立的,它有自己的執行執行緒,所以可以避免意外死鎖等問題。 實踐中,我們可以為自己的模組構建一個Cleaner,然後實現相應的清理邏輯。下面是 JDK自身提供的樣例程式:

     public class CleaningExample implements AutoCloseable {
     	// A cleaner, preferably one shared within a library
     	private static final Cleaner cleaner = <cleaner>;
     	static class State implements Runnable { State(...) {
     	// initialize State needed for cleaning action
     	}
     	public void run() {
     		// cleanup action accessing State, executed at most once
     	}
     	private final State;
     	private final Cleaner.Cleanable cleanable;
     	public CleaningExample() {
     		this.state = new State(...);
     		this.cleanable = cleaner.register(this, state);
     	}
     	public void close() {
     		cleanable.clean();
     	}
     }
    

注意,從可預測性的角度來判斷,Cleaner或者幻象引用改善的程度仍然是有限的,如果由於種種原因導致幻象引用堆積,同樣會出現問題。所以,Cleaner 適合作為一種最後的保證手段,而不是完全依賴 Cleaner 進行資源回收,不然我們就要再做一遍 finalize 的噩夢了。

精華評論:

石頭獅子:
列幾個 fianlly 不會被執行的情況:
1. try-cach 異常退出。
	try{
		system.exit(1)
	}finally{
		print(abc);
	}

2. 無限迴圈
try{
	while(ture){
		print(abc)
	}
}finally{
	print(abc)
}

3. 執行緒被殺死
當執行 try,finally 的執行緒被殺死時。finally 也無法執行。


總結
1,不要在 finally 中使用 return 語句。
2,finally 總是執行,除非程式或者執行緒被中斷。