我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
文章目录
- 一、Finalize
- 1.1 Finalize 让要GC的对象复活
- 1.2 防止子类乱来(需要的话)
- 二、 Cleaner 对比 Finalize
- 2.1 不同点
- 2.1.1 线程优先级: Finalize 低优先级。 Cleaner 属于高优先级。
- 2.1.2 finalizer中抛出的异常,无感知,Cleaner 没这个问题
- 2.2 相同点
- 2.2.1 都无法保证执行时间,对于时间敏感的操作,不允许利用这两者处理。
- 2.2.2 都可以作为最后一道防线(安全网 safenet)
- 2.2.3 都会影响性能
- 三、AutoCloseable 接口
- 3.1 AutoCloseable 使用实战
- 3.1.1 传统调用方法
- 3.1.2 继承 AutoCloseable ,使用try-with-resource 语法糖
- 3.1.3 文件IO中的实战使用
- 四、 lombok 的 @Cleanup
- 4.1 Cleanup 使用实战
- 4.1.1 demo
- 4.1.2 文件IO中的实战使用
- 五、总结
- 六、番外篇
效果等同 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
@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 ...
...
=== 点击查看top目录 ===
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)]
...
=== 点击查看top目录 ===
把全部 finalize 都定义成final,不让继承修改。
那么造成的后果就是,大量使用 Finalize 的话,假如有成千上万的对象在等待 Finalize 方法执行去释放资源,在cpu繁忙的情况下,没空去调用 Finalize 方法,那么有可能 OutOfMemoryError 使得 JVM死掉。
=== 点击查看top目录 ===
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
=== 点击查看top目录 ===
它甚至不能能保证它们是否会运行。当一个程序结束后,一些不可达对象上的Finalizer和Cleaner机制仍然没有运行。因此,不应该依赖于Finalizer和Cleaner机制来更新持久化状态。要是你靠这两个机制来释放分布式锁,那你就等着完蛋吧。
也不要期待以下方法为finalizer的执行提供保障:
System.gc();
System.runFinalization();
Runtime.runFinalizersOnExit(true);
System.runFinalizersOnExit(true);
=== 点击查看top目录 ===
InputStream内的 finalize 方法,用于关闭链接。但是由于GC时间不确定,所以关闭的时间也不确定,假如有一堆IO忘记关闭,还是会造成连接数上限的情况,这点要注意。
=== 点击查看top目录 ===
使用finalizer和cleaner机制会导致严重的性能损失。 在我的机器上,创建一个简单的AutoCloseable对象,使用try-with-resources关闭它,并让垃圾回收器回收它的时间大约是12纳秒。 使用finalizer机制,而时间增加到550纳秒。 换句话说,使用finalizer机制创建和销毁对象的速度要慢50倍。 这主要是因为finalizer机制会阻碍有效的垃圾收集。
=== 点击查看top目录 ===
// @since 1.7
public interface AutoCloseable {
void close() throws Exception;
}
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!
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!
=== 点击查看top目录 ===
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
跟 try-with-resource 效果一样。
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!
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);
}
}
}
=== 点击查看top目录 ===
下一章节:【JAVA Reference】Cleaner 在堆外内存DirectByteBuffer中的应用(五)
上一章节:【JAVA Reference】Cleaner 源码剖析(三)