記憶體是計算機很寶貴的資源,我們在使用資源時如果不關閉打開的資源,就有可能導致記憶體泄露的風險,下面淺析下Java中幾種常見的資源關閉方案
先定義一個資源類表示需要關閉的資源
public class MyResource implements Closeable {
@Override
public void close() throws IOException {
System.out.println("MyResource的close方法被呼叫!");
}
}
一、直接在try塊中關閉,如下代碼
public class FinallyTest {
public static void main(String[] args) {
try {
MyResource myResource = new MyResource();
System.out.println("拋出例外前...");
int i = 1 / 0;
myResource.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
我們可以看到輸出的結果是

其實我們可以看到,當拋出例外后try塊中例外后續的代碼就不會被執行,如當我們執行代碼int i = 1 / 0時,這句代碼肯定會拋出例外,當拋出例外后try塊中的代碼就不會被執行了,即后面的myRosource.close()方法就不會被呼叫,如果代碼中存在大量的資源呼叫,但是拋出了例外,就會導致記憶體泄露的風險,其實我們從位元組碼檔案中也可以知道結果位元組碼如下圖所示,

二、接下來我們看看使用try{...}catch{....}finally{...}的形式,接下來我們把代碼改為如下形式,把呼叫close的代碼移動到finally中,
public class FinallyTest {
public static void main(String[] args) throws IOException {
MyResource myResource = null;
try {
myResource = new MyResource();
System.out.println("拋出例外前...");
int i = 1 / 0;
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (myResource != null) {
myResource.close();
}
}
}
}
輸出結果如下,此時我們從輸出結果中可以看出,不管try塊中是否拋出例外,finally最終都會被執行

接下來我們從位元組碼的層面看看呼叫機制,其實我們可以看到,不管是走哪一個分支,最終都會執行finally的陳述句

三、try-with-resources 但是從JDK1.7開始,有新的方式,我們看介面AutoCloseable,是從JDK1.7開始提供的
/*
* @author Josh Bloch
* @since 1.7
*/
public interface AutoCloseable {
void close() throws Exception;
}
我們定義如下測驗代碼
public class FinallyTest {
public static void main(String[] args) throws IOException {
try (MyResource myResource = new MyResource()) {
System.out.println("拋出例外前...");
int i = 1 / 0;
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
執行結果如下,我們不需要顯示呼叫close方法,close會被呼叫,執行結果如下,

那java是如何實作的呢?我們反編譯位元組碼看看結果,這就是try...catch...finally的形式,try-catch-resource這種寫法只是一個語法糖,

但是細心的朋友可能發現var2.addSuppressed(var11)這個是什么鬼?我們去看看Throwable中方法addSuppressed的注釋,如下所示,可以看到,這個方法也是從JDK1.7開始的,省略了部分注釋,其實就是為了避免例外覆寫,
/**
* Appends the specified exception to the exceptions that were
* suppressed in order to deliver this exception. This method is
* thread-safe and typically called (automatically and implicitly)
* by the {@code try}-with-resources statement.
* @since 1.7
*/
public final synchronized void addSuppressed(Throwable exception) {
if (exception == this)
throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);
if (exception == null)
throw new NullPointerException(NULL_CAUSE_MESSAGE);
if (suppressedExceptions == null) // Suppressed exceptions not recorded
return;
if (suppressedExceptions == SUPPRESSED_SENTINEL)
suppressedExceptions = new ArrayList<>(1);
suppressedExceptions.add(exception);
}
接下來我們把MyResource的代碼改一下,讓close方法拋出例外,如下所示,
public class MyResource implements Closeable {
@Override
public void close() throws IOException {
System.out.println("MyResource的close方法被呼叫!");
throw new RuntimeException("我這里拋出例外啦....");
}
}
然后我們用傳統的try{...}catch{...}finally{...}看看輸出結果是什么樣的,
public static void main(String[] args) throws Exception {
MyResource myResource = new MyResource();
try {
System.out.println("拋出例外前....");
int i = 1 / 0;
} catch (Exception ex) {
throw ex;
} finally {
if (myResource != null) {
myResource.close();
}
}
}
輸出結果如下,

發現了嗎?堆疊里面沒有int i = 1 / 0 ,所產生的例外,例外被屏蔽了,針對這個問題,addSuppressed就可以派上用場啦,更多addSuppressed的使用可參考,
接下來我們看看try...with...Resource...的形式的解決方案,代碼如下
public static void main(String[] args) throws Exception {
try (MyResource myResource = new MyResource()) {
System.out.println("拋出例外前....");
int i = 1 / 0;
} catch (Exception ex) {
throw ex;
}
}
輸出結果如下:

看到了嗎?被append后面啦,
個人學習記錄
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/277467.html
標籤:其他
