背景知识
JAVA垃圾回收器原理
在JAVA中,当你创建一个对象时,JVM为该对象分配内存、调用构造函数并开始跟踪你使用的对象。当你停止使用一个对象(就是说,当没有对该对象有效的引用时),JVM通过垃圾回收器将该对象标记为释放状态。
当垃圾回收器将要释放一个对象的内存时,它调用该对象的finalize() 方法(如果该对象定义了此方法)。垃圾回收器以独立的低优先级的方式运行,只有当其他线程挂起等待该内存释放的情况出现时,它才开始运行释放对象的内存。
l "对象可以不被垃圾回收" : JAVA的垃圾回收遵循一个特点, 就是能不回收就不会回收。只要程序的内存没有达到即将用完的地步,对象占用的空间就不会被释放。因为如果程序正常结束了,而且垃圾回收器没有释放申请的内存, 那么随着程序的正常退出, 申请的内存会自动交还给操作系统; 而且垃圾回收本身就需要付出代价, 是有一定开销的, 如果不使用,就不会存在这一部分的开销。
l 垃圾回收只能回收内存, 而且只能回收内存中由JAVA创建对象方式(堆)创建的对象所占用的那一部分内存, 无法回收其他资源, 比如文件操作的句柄, 数据库的连接等等。
l 垃圾回收不是C++中的析构. 两者不是对应关系, 因为第一点就指出了垃圾回收的发生是不确定的, 而C++中析构函数是由程序员控制(delete) 或者离开器作用域时自动调用发生, 是在确定的时间对对象进行销毁并释放其所占用的内存。
l 调用垃圾回收器(System.gc())不一定保证垃圾回收器的运行
finalize方法
JAVA使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是不一定会被调用,即使是进程退出前。
class A {
public A() {
System.out.println("A()");
}
protected void finalize() {
System.out.println("~A()");
}
B b;
}
class B {
public B() {
System.out.println("B()");
}
protected void finalize() {
System.out.println("~B()");
}
}
public class Test{
public static void main(String[] args) {
A a = new A();
B b = new B();
a.b = b;
a = null;
}
}
运行结果
A() B()
分析:finalize() 是在JVM执行GC的时候才会执行的,但程序退出时GC并没有执行,即GC是否执行以及其执行的时机并不是可以精确控制的
注:发生GC时,一个对象的内存是否释放取决于是否存在该对象的引用,如果该对象包含对象成员,那对象成员也遵循本条。
class A {
public A() {
System.out.println("A()");
}
protected void finalize() {
System.out.println("~A()");
}
B b;
C c;
}
class B {
public B() {
System.out.println("B()");
}
protected void finalize() {
System.out.println("~B()");
}
}
class C {
public C() {
System.out.println("C()");
}
protected void finalize() {
System.out.println("~C()");
}
}
public class Test{
public static void main(String[] args) {
//对象成员变量仍然存在引用
A a1 = new A();
B b = new B();
a1.b = b;
a1.c = new C();
a1 = null;
//System.gc()建议垃圾回收器进行垃圾回收,但是不一会立即执行
System.gc();
}
}
运行结果
A() B() C() ~C() ~A()
* finalize方法运行流程
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
finalize执行流程
* finalize方法特点
l finalize()是Object的protected方法,JAVA中所有类都从Object类中继承finalize()方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
l finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),是在对象消亡时运行的。由于C++没有垃圾回收,对象空间手动回收,所以一旦对象用不到时,程序员就应当把它delete()掉。所以析构函数中经常做一些文件保存之类的收尾工作。但JAVA中的finalize()的调用具有不确定性,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说finalize()可能永远不被执行
l 子类继承父类的情况下,父类中的finalize()方法需要被显示的调用,即在子类中的finalize()块中需要显示调用super.finalize(),若没有调用super.finalize()方法,超类中finalize()方法将永远都不会被调用。
* finalize方法用途
finalize()其实是用来释放不是通过JAVA的new关键字分配的内存,建议用于:
- 清理本地对象(通过JNI创建的对象),比如说通过本地方法调用了C程序,C程序通过malloc分配了内存,那么垃圾回收器就不能通过JAVA语言来释放内存,只能在finalize方法内通过本地方法调用C程序进行释放内存
- 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。比如说InputStream,为了避免用户的误操作,可以在InputStream的finalize方法中关闭流,这样为资源的释放增加了一层保护网,虽然不保证一定能够执行。