Java核心技術----final、finally、 finalize的區別

1.概述

(1)final: 可以用來修飾類、方法、變量,分別有不同的意義,final 修飾的 class 代表不可以繼承擴展,final 的變量是不可以修改的,而 final 的方法也是不可以重寫的(override)。

  • final 變量產生了某種程度的不可變(immutable)的效果,所以,可以用於保護只讀數據,尤其是在併發編程中,因爲明確地不能再賦值 final 變量,有利於減少額外的同步開銷,也可以省去一些防禦性拷貝的必要。
  • 使用 final 修飾參數或者變量,也可以清楚地避免意外賦值導致的編程錯誤,甚至,有人明確推薦將所有方法參數、本地變量、成員變量聲明成 final。
  • 利用 final 可能有助於 JVM 將方法進行內聯,可以改善編譯器進行條件編譯的能力等等,很多類似的結論都是基於假設得出的,比如現代高性能 JVM(如 HotSpot)判斷內聯未必依賴 final 的提示,所以, final 字段對性能的影響,大部分情況下,並沒有考慮的必要。

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

  • 不要在 finally 中使用 return 語句 , 儘量不用break,continue
  • finally 總是執行,除非程序或者線程被中斷, 即在之前執行了System.exit(0)。。

(3)finalize: 是基礎類 java.lang.Object 的一個方法,它的設計目的是保證對象在被垃圾收集前完成特定資源的回收。finalize 機制現在已經不推薦使用,並且在 JDK 9 開始被標記爲 deprecated。
由於無法保證 finalize 什麼時候執行,執行的是否符合預期。使用不當會影響性能,導致程序死鎖、掛起, 所以不推薦使用。


2.具體分析

(1)final 並不等同於 immutable

(a)當final修飾對象時,那麼這個對象的引用不能變,但是值是不可以變的。

 final List<String> strList = new ArrayList<>();
 strList.add("Hello");
 strList.add("world");  
 List<String> unmodifiableStrList = List.of("hello", "world"); //jdk8新特性
 unmodifiableStrList.add("again");
  • final 只能約束 strList 這個引用不可以被賦值,但是 strList 對象行爲不被 final 影響,添加元素等操作是完全正常的。
    List.of 方法創建的本身就是不可變 List,最後那句 add 是會在運行時拋出異常的。

(b)Immutable 在很多場景是非常棒的選擇, Java 語言目前並沒有原生的不可變支持,如果要實現 immutable 的類,我們需要做到:

  • 將 class 自身聲明爲 final
  • 將所有成員變量定義爲 private 和 final,並且不要實現 setter 方法。
  • 通常構造對象時,成員變量使用 深度拷貝 (深拷貝一個對象, 這個對象必須要實現Cloneable接口,實現clone方法,並且在clone方法內部,把該對象引用的其他對象也要clone一份 , 這就要求這個被引用的對象必須也要實現Cloneable接口並且實現clone方法)來初始化,而不是直接賦值,這是一種防禦措施,因爲你無法確定輸入對象不被其他人修改。
  • 如果確實需要實現 getter 方法,或者其他可能會返回內部狀態的方法,使用 copy-on-write 原則 (在併發訪問的情景下,當需要修改JAVA中Containers的元素時,不直接修改該容器,而是先複製一份副本,在副本上進行修改。修改完成之後,將指向原來容器的引用指向新的容器(副本容器)),創建私有的 copy。

(2)finalize爲什麼不推薦使用

(a)finalize 的執行是和垃圾收集關聯在一起的,一旦實現了非空的 finalize 方法,就會導致相應對象回收呈現數量級上的變慢,有人專門做過 benchmark,大概是 40~50 倍的下降。

  • 因爲,finalize 被設計成在對象被垃圾收集前調用,這就意味着實現了 finalize 方法的對象是個“特殊公民”,JVM 要對它進行額外處理。finalize 本質上成爲了快速回收的阻礙者,可能導致你的對象經過多個垃圾收集週期才能被回收。
  • 如果用 System.runFinalization​() 讓JVM 積極一點,也許有點用,但是問題在於,這還是不可預測、不能保證的,所以本質上還是不能指望。實踐中,因爲 finalize 拖慢垃圾收集,導致大量對象堆積,也是一種典型的導致 OOM 的原因。

因此,我們需要遵循一個原則: 資源用完即顯式釋放,或者利用資源池來儘量重用。

(b)finalize 還會掩蓋資源回收時的出錯信息, 分析以下代碼, 發現這裏的Throwable 是被生吞了的!也就意味着一旦出現異常或者出錯,你得不到任何有效信息。況且,Java 在 finalize 階段也沒有好的方式處理任何信息,不然更加不可預測。

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(); 
 }

(3)替換 finalize的機制

java 平臺目前在逐步使用 java.lang.ref.Cleaner 來替換掉原有的 finalize 實現。

  • Cleaner 的實現利用了幻象引用(PhantomReference),這是一種常見的所謂 post-mortem 清理機制。利用幻象引用和引用隊列,我們可以保證對象被徹底銷燬前做一些類似資源回收的工作,比如關閉文件描述符(操作系統有限的資源),它比 finalize 更加輕量、更加可靠。吸取了 finalize 裏的教訓,每個 Cleaner 的操作都是獨立的,它有自己的運行線程,所以可以避免意外死鎖等問題。示例如下:
   public class CleaningExample implements AutoCloseable { //jdk1.7
        // 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 的噩夢了。
  • 上面的示例代碼中,將 State 定義爲 static,就是爲了避免普通的內部類隱含着對外部對象的強引用,因爲那樣會使外部對象無法進入幻象可達的狀態。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章