我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼
- Clean 直接看上一章 【JAVA Reference】Cleaner 源碼剖析(三)
一、Finalize
效果等同 Cleaner,都是在 GC 前搞點事情。
public class _05_01_TestCleanerFinalize {
public static void main(String[] args) throws Exception {
int index = 0;
while (true) {
Thread.sleep(1000);
// 提醒 GC 去進行垃圾收集了
System.gc();
// 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
DemoObject obj = new DemoObject("demo" + index++);
}
}
@Data
@AllArgsConstructor
@ToString
static class DemoObject {
private String name;
// private static final List<Object> objectList = Lists.newArrayList();
@Override
protected void finalize() throws Throwable {
System.out.println(name + " running DoSomething ...");
// objectList.add(this);
// System.out.println(objectList.size());
// System.out.println(objectList);
}
}
}
輸出:
demo0 running DoSomething ...
demo1 running DoSomething ...
demo2 running DoSomething ...
demo3 running DoSomething ...
demo4 running DoSomething ...
...
1.1 Finalize 讓要GC的對象復活
public class _05_01_TestCleanerFinalize {
public static void main(String[] args) throws Exception {
int index = 0;
while (true) {
Thread.sleep(1000);
// 提醒 GC 去進行垃圾收集了
System.gc();
// 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
DemoObject obj = new DemoObject("demo" + index++);
}
}
@Data
@AllArgsConstructor
@ToString
static class DemoObject {
private String name;
private static final List<Object> objectList = Lists.newArrayList();
@Override
protected void finalize() throws Throwable {
System.out.println(name + " running DoSomething ...");
objectList.add(this);
System.out.println(objectList.size());
System.out.println(objectList);
}
}
}
輸出:
demo1 running DoSomething ...
2
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1)]
demo2 running DoSomething ...
3
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1), _05_01_TestCleanerFinalize.DemoObject(name=demo2)]
demo3 running DoSomething ...
4
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1), _05_01_TestCleanerFinalize.DemoObject(name=demo2), _05_01_TestCleanerFinalize.DemoObject(name=demo3)]
demo4 running DoSomething ...
5
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1), _05_01_TestCleanerFinalize.DemoObject(name=demo2), _05_01_TestCleanerFinalize.DemoObject(name=demo3), _05_01_TestCleanerFinalize.DemoObject(name=demo4)]
...
1.2 防止子類亂來(需要的話)
把全部 finalize 都定義成final,不讓繼承修改。
二、 Cleaner 對比 Finalize
2.1 不同點
2.1.1 線程優先級: Finalize 低優先級。 Cleaner 屬於高優先級。
那麼造成的後果就是,大量使用 Finalize 的話,假如有成千上萬的對象在等待 Finalize 方法執行去釋放資源,在cpu繁忙的情況下,沒空去調用 Finalize 方法,那麼有可能 OutOfMemoryError 使得 JVM死掉。
=== 點擊查看top目錄 ===
2.1.2 finalizer中拋出的異常,無感知,Cleaner 沒這個問題
public class _05_02_TestCleanerVsFinalize {
public static void main(String[] args) throws Exception {
// testFinalizeException();
testCleanerException();
}
private static void testCleanerException() throws Exception{
int index = 0;
while (true) {
Thread.sleep(1000);
// 提醒 GC 去進行垃圾收集了
System.gc();
// 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
Object obj = new Object();
/*
增加 obj 的虛引用,定義清理的接口 DoSomethingThread
第一個參數:需要監控的堆內存對象
第二個參數:程序釋放資源前的回調。
*/
Cleaner.create(obj, new DoSomethingThread("thread_" + index++));
}
}
private static void testFinalizeException() throws Exception{
int index = 0;
while (true) {
Thread.sleep(1000);
// 提醒 GC 去進行垃圾收集了
System.gc();
// 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
DemoObject obj = new DemoObject("demo" + index++);
System.out.println(" running DoSomething ...");
System.out.println(10 / 0);
//這個地方死了,上面有異常,不走到這,可是確連個異常都不跑出來
System.out.println(" running DoSomething End ...");
}
}
@Data
@AllArgsConstructor
@ToString
static class DemoObject {
private String name;
@Override
protected void finalize() throws Throwable {
System.out.println(name + " running DoSomething ...");
System.out.println(10 / 0);
//這個地方死了,上面有異常,不走到這,可是確連個異常都不打印到出來
System.out.println(name + " running DoSomething End ...");
}
}
static class DoSomethingThread implements Runnable {
private String name;
public DoSomethingThread(String name) {
this.name = name;
}
// do something before gc
@Override
public void run() {
System.out.println(name + " running DoSomething ...");
System.out.println(10 / 0);
System.out.println(name + " running DoSomething End ...");
}
}
}
結論:finalize() 方法內部 10/0,出了問題,但是日誌不打出來,程序也不往下走。
爲什麼會這樣?Finalizer 剖析 (六)#4.9.3.1
2.2 相同點
2.2.1 都無法保證執行時間,對於時間敏感的操作,不允許利用這兩者處理。
它甚至不能能保證它們是否會運行。當一個程序結束後,一些不可達對象上的Finalizer和Cleaner機制仍然沒有運行。因此,不應該依賴於Finalizer和Cleaner機制來更新持久化狀態。要是你靠這兩個機制來釋放分佈式鎖,那你就等着完蛋吧。
也不要期待以下方法爲finalizer的執行提供保障:
System.gc();
System.runFinalization();
Runtime.runFinalizersOnExit(true);
System.runFinalizersOnExit(true);
2.2.2 都可以作爲最後一道防線(安全網 safenet)
InputStream內的 finalize 方法,用於關閉鏈接。但是由於GC時間不確定,所以關閉的時間也不確定,假如有一堆IO忘記關閉,還是會造成連接數上限的情況,這點要注意。
=== 點擊查看top目錄 ===
2.2.3 都會影響性能
使用finalizer和cleaner機制會導致嚴重的性能損失。 在我的機器上,創建一個簡單的AutoCloseable對象,使用try-with-resources關閉它,並讓垃圾回收器回收它的時間大約是12納秒。 使用finalizer機制,而時間增加到550納秒。 換句話說,使用finalizer機制創建和銷燬對象的速度要慢50倍。 這主要是因爲finalizer機制會阻礙有效的垃圾收集。
=== 點擊查看top目錄 ===
三、AutoCloseable 接口
// @since 1.7
public interface AutoCloseable {
void close() throws Exception;
}
3.1 AutoCloseable 使用實戰
3.1.1 傳統調用方法
public class _06_00_TestAutoCloseable{
public static void main(String[] args) {
_06_00_TestAutoCloseable instance = new _06_00_TestAutoCloseable();
try{
instance.doIt();
}finally {
instance.close();
}
}
public void doIt() { System.out.println("MyAutoClosable doing it!"); }
public void close() { System.out.println("MyAutoClosable closed!"); }
}
輸出:
MyAutoClosable doing it!
MyAutoClosable closed!
3.1.2 繼承 AutoCloseable ,使用try-with-resource 語法糖
public class _06_01_TestAutoCloseable implements AutoCloseable {
public static void main(String[] args) {
try(_06_01_TestAutoCloseable instance = new _06_01_TestAutoCloseable()){
instance.doIt();
}
}
public void doIt() { System.out.println("MyAutoClosable doing it!"); }
@Override
public void close() {
System.out.println("MyAutoClosable closed!");
}
}
輸出:
MyAutoClosable doing it!
MyAutoClosable closed!
3.1.3 文件IO中的實戰使用
- 使用 try-with-resource
public class _06_02_TestAutoCloseable {
public static void main(String[] args){
try(BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))){
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedInputStream 與 BufferedOutputStream 都實現了 AutoCloseable
- 看 UML 圖
=== 點擊查看top目錄 ===
四、 lombok 的 @Cleanup
跟 try-with-resource 效果一樣。
4.1 Cleanup 使用實戰
4.1.1 demo
public class _06_02_TestAutoCloseable implements AutoCloseable {
public static void main(String[] args) {
@Cleanup
_06_01_TestAutoCloseable instance = new _06_01_TestAutoCloseable();
instance.doIt();
}
public void doIt() { System.out.println("MyAutoClosable doing it!"); }
@Override
public void close() {
System.out.println("MyAutoClosable closed!");
}
}
輸出:
MyAutoClosable doing it!
MyAutoClosable closed!
4.1.2 文件IO中的實戰使用
- 使用 @Cleanup
public class _06_04_TestAutoCloseable {
public static void main(String[] args) throws Exception {
@Cleanup BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
@Cleanup BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));
int b;
while ((b = bin.read()) != -1){
bout.write(b);
}
}
}
五、總結
- 儘量避免使用 Cleaner 與 finalize
六、番外篇
下一章節:【JAVA Reference】Cleaner 在堆外內存DirectByteBuffer中的應用(五)
上一章節:【JAVA Reference】Cleaner 源碼剖析(三)