1. 程式人生 > >靜態程式碼掃描(四)——Java資源關閉研究

靜態程式碼掃描(四)——Java資源關閉研究

最近一直在研究java資源關閉的檢查規則,發現市面上開源的工具針對資源關閉的檢測都存在一定不足,同時也無法滿足我們業務的需求。所以火線團隊針對資源關閉進行了深度的研究,取得了一些不錯的進展,但是過程的艱辛也遠超了我們的預料。現在就跟大家聊聊我們的心路歷程,從為什麼開始。

1. 為什麼要手動關閉Java資源物件?

首先解釋Java的資源物件,它主要包括IO物件,資料庫連線物件。比如常見的InputStream、OutputStream、Reader、Writer、Connection、Statement、ResultSet、Socket等等,先程式碼列舉一個示例:

FileInputStream f = new
FileInputStream("sample.txt"); f.close();//f物件即需要手動關閉的資源物件

上述程式碼中f物件即需要手動關閉的資源物件。
如果類似的資源物件沒有及時的手動關閉,這個物件就會一直佔據記憶體,當這樣的物件越來越多,那記憶體被佔用的就會越來越多,久而久之就可能造成OutOfMemory,俗稱記憶體溢位。

記憶體溢位案例

這時應該有人會問,Java不是有自己的垃圾回收機制GC麼?不是可以自動回收麼?
這個問題問的好,我也一度非常困惑。
首先我們先了解一下GC的原理:
在Java中,當沒有物件引用指向原先分配給某個物件的記憶體時,該記憶體便成為垃圾。JVM的一個系統級執行緒會自動釋放該記憶體塊。垃圾回收意味著程式不再需要的物件是”無用資訊”,這些資訊將被丟棄。當一個物件不再被引用的時候,記憶體回收它佔領的空間,以便空間被後來的新物件使用。

這裡寫圖片描述

首先GC只能回收記憶體。至於各種stream之類,他們下邊一般還開啟了各種其他的系統資源,比如檔案,比如輸入輸出裝置(鍵盤/螢幕等),等等。而這些裝置第一是不能自動關閉(因為誰知道你程式要用它到什麼時候啊),另一個系統內數量有限(比如鍵盤/螢幕同一時間只有一個)。最後,檔案和資料庫連線之類的東西還存在讀寫鎖定的問題。這些都導致使用者必須手動處理這些資源的開啟和關閉。

其次為了“避免”程式設計師忘了自己釋放那些資源,Java提供了finalizer、PhantomReference之類的機制來讓程式設計師向GC註冊“自動回撥釋放資源”的功能。但GC回撥它們的時機不確定,所以只應該作為最後手段來使用,主要手段還是自己關閉最好。

PS:關於GC其實有很多的知識可以深度挖掘,比如各種回收演算法,finalize()方法等等,大家感興趣的話可以自行搜尋研究,我就不班門弄斧了。

2. 怎樣正確的手動關閉Java資源物件?

先說一種最常見的關閉方式,在finally中進行關閉:

FileInputStream f;
try{
    f= new FileInputStream("sample.txt");
    //something that uses f and sometimes throws an exception
}
catch(IOException ex){
    /* Handle it somehow */
}
finally{
    f.close();
}

這裡在finally中進行資源物件關閉屬於Best Practice。因為即使物件f在使用的過程中出現異常,也能保證程式不會跳過後續的關閉操作。

特別注意,自從Java1.7開始,支援了try-with-resources寫法,即將資源物件宣告的過程放在try()的括號裡面,這樣java在資源物件使用完成之後會自動關閉。

    try (
            FileOutputStream fileOutputStream = new FileOutputStream("E:\\A.txt");
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            DataOutputStream out = new DataOutputStream(bufferedOutputStream)
            )
    {       
        out.write(data1);
    } catch (Exception e) {
        // TODO: handle exception
    }

另外還有一些第三方庫提供了一些統一的關閉處理方法,例如

import org.apache.commons.io.IOUtils;
public static void main(String[] args) throws Exception{
    FileOutputStream fileOutputStream = null;
    BufferedOutputStream bufferedOutputStream=null;
    DataOutputStream out=null;
    byte[] data1 = "這個例子測試檔案寫".getBytes("GB2312");
    try {       
        fileOutputStream = new FileOutputStream("E:\\A.txt");
        bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        out = new DataOutputStream(bufferedOutputStream);
        out.write(data1);
    } catch (Exception e) {
        // TODO: handle exception
    } finally {
        IOUtils.closeQuietly(out);
    }
}

這個apache提供的IOUtils類庫可以通過IOUtils.closeQuietly(e)的形式關閉資源物件,實際內部實現依然是呼叫.close()方法。內部實現程式碼如下:

public static void closeQuietly(final Closeable closeable) {
337       try {
338            if (closeable != null) {
339                closeable.close();
340            }
341        } catch (final IOException ioe) {
342            // ignore
343        }
344    }

以上就是手動關閉Java資源物件的幾種推薦寫法,希望對大家有所幫助。

為防止篇幅過長,這只是系列文章的第一篇,我將在下一篇繼續講述在判斷資源關閉時,有哪些不為人知的特殊情況需要考慮。
敬請期待。

參考文獻: