Java中有析構函數嗎?
一、概述
在這個簡短的教程中,我們將研究在 Java 中銷毀對象的可能性。
2.Java中的析構函數
每次我們創建一個對象時,Java 都會自動在堆上分配內存。同樣,每當不再需要某個對象時,內存將自動被釋放。
在像 C 這樣的語言中,當我們在內存中使用完一個對象時,我們必須手動釋放它。不幸的是, Java 不支持手動內存釋放。此外,Java 編程語言的一個特性是自己處理對象銷毀——使用一種稱為垃圾收集的技術。
3. 垃圾收集
垃圾收集從堆上的內存中刪除未使用的對象。它有助於防止內存洩漏。簡單地說,當沒有更多對特定對象的引用並且該對像不再可訪問時,垃圾收集器將該對象標記為不可訪問並回收其空間。
未能正確處理垃圾收集可能會導致性能問題,並最終導致應用程序內存不足。
當對象達到程序中不再可訪問的狀態時,可以對對象進行垃圾回收。當出現以下兩種情況之一時,對像不再可達:
- 該對像沒有任何指向它的引用
- 對該對象的所有引用都超出範圍
Java 包含System.gc()
方法來幫助支持垃圾收集。通過調用這個方法,我們可以建議JVM運行垃圾收集器。但是,我們不能保證 JVM 會真正調用它。 JVM 可以自由地忽略該請求。
4.終結器
Object類提供finalize()
方法。在垃圾收集器從內存中刪除一個對象之前,它會調用finalize()
方法。該方法可以運行零次或一次。但是,它不能為同一個對象運行兩次。
Object
類中定義的finalize()
方法不執行任何特殊操作。
終結器的主要目標是在對像從內存中刪除之前釋放它使用的資源。例如,我們可以覆蓋關閉數據庫連接或其他資源的方法。
讓我們創建一個包含BufferedReader
實例變量的類:
class Resource {
final BufferedReader reader;
public Resource(String filename) throws FileNotFoundException {
reader = new BufferedReader(new FileReader(filename));
}
public long getLineNumber() {
return reader.lines().count();
}
}
在我們的示例中,我們沒有關閉資源。我們可以在裡面關閉它們 finalize()
方法:
@Override
protected void finalize() {
try {
reader.close();
} catch (IOException e) {
// ...
}
}
當 JVM 調用finalize()
方法時, BufferedReader
資源將被釋放。 finalize()
方法拋出的異常將停止對象的終結。
但是,從 Java 9 開始, finalize()
方法已被棄用。使用finalize()
方法可能會令人困惑並且難以正確使用。
如果我們想釋放對象持有的資源,我們應該考慮實現AutoCloseable
接口。像[Cleaner](https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html)
和PhantomReference
這樣的類提供了一種更靈活的方式來在對像變得無法訪問時管理資源。
4.1。實現AutoCloseable
AutoCloseable
接口提供close()
方法,該方法將在退出try-with-resources
塊時自動執行。在這個方法中,我們可以關閉一個對象使用的資源。
讓我們修改我們的示例類以實現AutoCloseable
接口:
class Resource implements AutoCloseable {
final BufferedReader reader;
public Resource(String filename) throws FileNotFoundException {
reader = new BufferedReader(new FileReader(filename));
}
public long getLineNumber() {
return reader.lines().count();
}
@Override
public void close() throws Exception {
reader.close();
}
}
我們可以使用close()
方法來關閉我們的資源,而不是使用finalize()
方法。
4.2. Cleaner
類
如果我們想在對像變為幻像可訪問時執行特定操作,我們可以使用Cleaner
類。換句話說,當一個對象完成並且它的內存準備好被釋放時。
現在,讓我們看看如何使用Cleaner
類。首先,讓我們定義Cleaner
:
Cleaner cleaner = Cleaner.create();
接下來,我們將創建一個包含更清晰引用的類:
class Order implements AutoCloseable {
private final Cleaner cleaner;
public Order(Cleaner cleaner) {
this.cleaner = cleaner;
}
}
其次,我們將在Order
類中定義一個實現Runnable
的靜態內部類:
static class CleaningAction implements Runnable {
private final int id;
public CleaningAction(int id) {
this.id = id;
}
@Override
public void run() {
System.out.printf("Object with id %s is garbage collected. %n", id);
}
}
我們內部類的實例將代表清潔動作。我們應該註冊每個清理操作,以便它們在對像變為幻像可訪問後運行。
我們應該考慮不使用 lambda 進行清理操作。通過使用 lambda,我們可以輕鬆地捕獲對象引用,從而防止對像變為幻像可達。如上所述,使用靜態嵌套類將避免保留對象引用。
讓我們在Order
類中添加Cleanable
實例變量:
private Cleaner.Cleanable cleanable;
Cleanable
實例表示包含清潔動作的清潔對象。
接下來,讓我們創建一個註冊清理操作的方法:
public void register(Product product, int id) {
this.cleanable = cleaner.register(product, new CleaningAction(id));
}
最後,讓我們實現close()
方法:
public void close() {
cleanable.clean();
}
clean()
方法註銷可清理對象並調用已註冊的清理操作。無論調用多少次 clean,這個方法最多會被調用一次。
當我們在try-with-resources
塊中使用CleaningExample
實例時, close()
方法會調用清理操作:
final Cleaner cleaner = Cleaner.create();
try (Order order = new Order(cleaner)) {
for (int i = 0; i < 10; i++) {
order.register(new Product(i), i);
}
} catch (Exception e) {
System.err.println("Error: " + e);
}
在其他情況下,當實例變為幻像可訪問時,清理器將調用clean()
方法。
此外,在System.exit()
期間清理程序的行為是特定於實現的。 Java 不保證是否會調用清理操作。
5. 結論
在這個簡短的教程中,我們研究了 Java 中對象破壞的可能性。綜上所述,Java 不支持手動對象銷毀。但是,我們可以使用 finalize()
或 Cleaner
釋放對象持有的資源。與往常一樣,示例的源代碼是可用的 在 GitHub 上。