gc
:垃圾回收
finalize()
:是类似于析构函数的东西,如果覆写了该方法,则在垃圾回收过程中会调用该方法,然后再销毁对象的资源。
gc是一个很好的设计思路,类似于C++的RAII,可以让开发人员专注于业务逻辑代码,而不必太关心对象的销毁和资源的释放。
但是如果我们要用gc,仅仅用到gc这一步就好。如果你需要finalize帮你做一些额外的事情,打住,建议不要使用了(虽然并不是任何时候使用都会有问题,但是如果有其它替代手段,不要用它)。
gc机制在理不再赘述,这是java区别于C++的重大特性之一,到处可以找到资料。主要说下finalize()的使用场景。
例如你在构造器中打开了一个文件,gc只会帮你销毁当前的对象,而不会帮你关闭打开的文件资源。这是一个使用finalize的典型场景。(很像C++的析构,稍后你会看到跟C++析构的区别)
finalize()到底有什么问题,会导致人们对它敬而远之。
一句话:它什么时候执行是无法预知的,也就是说,并不存在任何一种机制可以把资源的释放与对象的生命周期完全绑定在一起
。(这是它跟C++的析构本质的区别,C++ delete对象的时机和析构的时机是完全一致的,使用者控制了delete对象的时机,其实也就控制了析构的时机,对于C++来讲,带来的收益是不需要再手动去执行析构函数中那一堆逻辑)
但是java的野心远不止于此,他把finalize()跟gc机制结合起来。导致了这种运行时机不可控。
大概看下gc的运行机制:
从图中可以看到,一个低优先级队列来执行finalize。因此,虽然对象当前已经无人引用,但是需要等待finalize执行完才能销毁该对象,而这个销毁是低优先级执行的,时机不能确定。
用代码来说明下(不同机器的运行结果可能有差异):
class MyObj {
static int totalCreatedObjNum = 0;
static int totalFinalizedObjNum = 0;
private final int SIZE = 10000;
private int[] array = new int[SIZE];
private int id;
MyObj(int id) {
totalCreatedObjNum++;
this.id = id;
System.out.format("obj(%d) created\n", id);
}
@Override
protected void finalize() {
totalFinalizedObjNum++;
System.out.format("Obj(%d) finalize() called\n", id);
}
}
public class Finalization {
static private int count = 0;
public static void main(String[] args) {
while (count < 500) {
count++;
new MyObj(count); // 无该对象的引用者
// System.gc();
}
System.out.format("totalCreatedObj: %d, totalFinalizedObj: %d\n",
MyObj.totalCreatedObjNum, MyObj.totalFinalizedObjNum);
}
}
运行结果:
...
obj(491) created
obj(492) created
obj(493) created
obj(494) created
obj(495) created
obj(496) created
obj(497) created
obj(498) created
obj(499) created
obj(500) created
totalCreatedObj: 500, totalFinalizedObj: 264
Obj(363) finalize() called
Obj(360) finalize() called
Obj(356) finalize() called
从运行结果可以看出,gc进行对象的销毁时机是不一定的,创建了500个对象时其实只销毁了264个,后面还在陆续销毁。如果例子中的SIZE很大,而销毁不及时的话(可能创建的快销毁的慢),会导致内存耗尽。
那如果我手动执行System.gc()呢?(放开上例中System.gc()的注释)。运行结果:
...
Obj(493) finalize() called
Obj(494) finalize() called
obj(495) created
obj(496) created
Obj(495) finalize() called
Obj(496) finalize() called
obj(497) created
obj(498) created
obj(499) created
Obj(497) finalize() called
Obj(499) finalize() called
Obj(498) finalize() called
obj(500) created
totalCreatedObj: 500, totalFinalizedObj: 499
Obj(500) finalize() called
可以看出,能释放的快一些,但是由于处理finalize的处理优先级较低,释放仍然是不及时的。并且这种手动频繁调用System.gc的方式显然对系统的性能是有很多损耗的,实际肯定不能这么用。
所以应该去掉finalize()的实现,让gc自己去释放。
结论
:如果真的有需要在对象销毁时额外释放的资源,则自行调用,不要依赖Finalize。
例如:
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
class Finalization {
private BufferedReader fd;
void Process() {
try {
fd = new BufferedReader(new FileReader("C:\\Users\\Administrator\\IdeaProjects\\demo2\\src\\1.txt"));
// do something
} catch (FileNotFoundException e) {
throw new RuntimeException("open file fail");
}
}
void Destruct() {
try {
fd.close();
} catch (IOException e) {}
}
public static void main(String[] args) {
Finalization obj = new Finalization();
obj.Process();
obj.Destruct(); // 手动析构
}
}
显式调用Destruct方法释放资源,而不是跟对象的销毁绑在一起。
更有趣的是,可以在Finalize中让自己再次被引用的方式延迟死亡(只能生效一次)。但这些都是很诡异的用法,可以知道,但不建议用。(参考:https://blog.csdn.net/weixin_39913200/article/details/85955174)
这也许就是finalize()设计者当初考虑不全面的一个后果吧,而且finalize()属于Object的一个方法,抽象层次如此高的类中错误设计了一个方法,想去掉它是相当困难,所以当前我用的jdk14中,看到finalize()已经被deprecated了(通过idea能看到被划掉了)。当然也只是个建议不要使用,实际存量代码中,很多还是有使用该机制的,所以要完全弃用还需要考虑兼容性问题。
网友评论