Java垃圾回收机制
Java有垃圾回收期负责回收无用对象占据的内存空间。但也有特殊情况:假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收期只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存。
Java允许在类中定义一个名为finalize()方法。一旦垃圾回收期准备好释放对象占用的内存空间,首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
在Java中,对象并非总是被垃圾回收,只要程序没有濒临内存空间用完的那一刻,对象占用的空间就中也得不到释放。如果程序执行结束,并且垃圾回收器一直没有释放创建的对象,程序结束退出后资源也会全部返回给操作系统。
这个垃圾回收策略避免了频繁垃圾回收造成的开销,Java虚拟机并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的
finalize()用途
不论对象是如何创建的,垃圾回收器都会负责释放对象的所有内存。
finalize()需求限制到一种特殊情况:通过某种创建对象方式以外的方式为对象分配了存储空间。
需要使用finalize()的原因,是由于在分配内存时可能采用了C语言中的做法,而非Java的通常做法。主要发生在使用本地方法的情况下。
在非Java代码中,也许会调用C语言的malloc()函数系列来分配存储空间,而且除非用了free()函数,存储空间将得不到释放,从而造成内存泄漏。这时需要在finalize中用本地方法调用它。
不要过多使用finalize(),它不是进行普通清理工作的方法。避免使用finalize()函数,finalize()函数无法预料,常常是危险的,总之是多余的。
finalize()用法示例:对象终结条件的验证
在对象不在被引用时,finalize()方法会被垃圾收集器调用。
通常不能指望finalize(),必须创建其他的“清理”方法,并且明确的调用它们。
这里有一个应用场景:终结条件的验证。
当对某个对象不再感兴趣了,对象就可以被清理了。这个对象应该处于某种状态,使它占用的内存可以被安全地释放。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的缺陷。finalize()可以用来发现最终发现这种情况,尽管它并不总会被调用。如,打开的文件,在垃圾回收前程序员应该关闭这个文件等。
以下示例改编自《Java编程思想》
//: initialization/TerminationCondition.java
// Using finalize() to detect an object that
// hasn't been properly cleaned up.
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
@Override
protected void finalize() {
try {
if(checkedOut) {
System.out.println("Error: checked out");
}
}finally {
try {
super.finalize();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
// Proper cleanup:
novel.checkIn();
// Drop the reference, forget to clean up:
new Book(true);
// Force garbage collection & finalization:
System.gc();
}
} /* Output:
Error: checked out
*///:~
本例的终结条件是:所有的Book对象在被当做垃圾回收前应该处于checkOut==false的状态。这里可以通过finalize()来验证终结条件,发现程序设计中的缺陷。
System.gc()用于强制进行终结动作。 如果finalize方法抛出未捕获的异常,该异常被忽略,并终止对象的回收。
Java9中的垃圾回收机制
finalize机制本身就是存在问题的。
finalize机制可能会导致性能问题,死锁和线程挂起。
finalize中的错误可能导致内存泄漏;如果不在需要时,也没有办法取消垃圾回收;并且没有指定不同执行finalize对象的执行顺序。此外,没有办法保证finlize的执行时间。
遇到这些情况,对象调用finalize方法只有被无限期延后。
Java9中finalize方法已经被废弃。
占有非堆资源的对象实例,类应该提供一个方法以明确释放这些资源,如果合适的话他们也应该实现AutoCloseable接口。
java.lang.ref.Cleaner和java.lang.ref.PhantomReference提供更灵活和有效的方式,在对象无法再访问时释放资源。
Cleaner示例
import java.lang.ref.Cleaner;
public class CleaningExample implements AutoCloseable {
// A cleaner, preferably one shared within a library
private static final Cleaner cleaner = Cleaner.create();
static class State implements Runnable {
State() {
System.out.println("init");// initialize State needed for cleaning action
}
public void run() {
System.out.println("clean");// cleanup action accessing State, executed at most once
}
}
private final State state;
private final Cleaner.Cleanable cleanable;
public CleaningExample() {
this.state = new State();
this.cleanable = cleaner.register(this, state);
}
public void close() {
cleanable.clean();
}
public static void main(String[] args) {
while(true) {
new CleaningExample();
}
}
}
本例中每次创建对象时,都会打印init;回收对象时,都会打印clean。