1.回收对象占用的内存
对于内存的回收,JAVA与C++不同。在C++中,对象占用的内存,其回收是明确无误的。在栈中分配的对象,当代码的运行离开对象的作用域时,系统自动调用对象的析构函数,然后回收内存。对于在堆中为对象分配的内存,即使离开分配时的作用域,系统也不回收内存,只有开发者调用如delete这样的操作符时,系统才会调用对象的析构函数,然后回收内存。如果发开者忘记调用delete回收堆中分配的对象占用的内存,就内存泄露了。
而在JAVA中,所有对象都是在堆中分配的,并且对象没有析构函数。开发者无需关心对象占用的内存回收问题。Java实现了垃圾自动回收机制。如下面这样:
1 public static void main(String[] args) {
2 Test t = new Test();
3 t = new Test();
4 t = null;
5 }
在第二行为t创建了对象,此时新创建的对象是可达的,因为有"t"这个句柄指向它,所以这个对象不会被当成垃圾回收。
在第三行为t重新创建了对象,此时在第二行为t创建的对象就没有句柄指向它,它变成不可达的状态,这个时候第二行分配的对象就可以当成垃圾回收了。
在第四行将t放空。同理,第三行分配的对象也变成不可达,这个时候第三行创建的对象也可以回收了。
这种机制使程序员不用再关心内存的回收问题,只管创建不管回收,回收由系统负责,当然也就避免了因为程序员粗心而造成的内存泄漏。当然代价就是效率,内存的回收由系统控制,什么时候回收是不确定的。
2.回收对象占用的非内存资源
对象占用的内存资源,由系统自动回收。但对象还可能占用其它资源,如对数据库的链接,这种资源在不用的时候也一定要回收,这个垃圾回收并不负责。在C++中,这种类型资源的回收由析构函数控制,但是Java中没有析构函数,但它也有类似的机制。一种是finalize机制,另一种是finally机制。
2.1.finalize机制
finalize机制存在很多问题,如资源回收不及时,是不是真的会回收不确定等,并不是一种推荐的回收资源方法,但需要了解一下它的运行机制。
finalize是Object类中的一个方法,Java类中的所有类都是单根结果,都是由Object类派生而来。如果在派生类中实现了finalize方法,那么系统在回收对象占用的内存之前会先调用finalize方法,以释放对象占用的其它资源,然后再回收内存资源。当然,如果不为对象实现finallize方法,那么系统在对象占用的资源之前,自然也就不会调用finallize方法,直接回收就行了。
Java中的对象有两组状态,一种是可达状态,包括(reachable,finalize-reachable,unreachable)。当然如果对象没有实现finalize方法,它就不会有finalize-reachable状态,只有(reachable,unreachable)两种状态。没有实现finallize方法的对象,它的状态变化很简单,只有两种状态,当有句柄指向它时,就是reachable状态,如Test t = new Test(),这个时候不回收内存。接来来随着代码的运行,有t=null这句话,则t原来指向的对象就变成unreachable状态了,这个时候系统就可以回收内存了。如果对象实现了finallize方法,则其状态的变化要复杂一些,接下来说明。
另一组是结束状态,也就是finallize状态,前边说了,只有实现了finallize方法的对象才有这种状态。它也有三个阶段(unfinallized、finallizable、finallized)。这种类型的对象状态变化比较复杂,还涉及到对象复活的问题,通过一段代码说明:
public class GC {
public static GC SAVE_HOOK = null;
public static void main(String[] args) throws InterruptedException {
// 新建对象,因为SAVE_HOOK指向这个对象,对象此时的状态是(reachable,unfinalized)
SAVE_HOOK = new GC();
// 将SAVE_HOOK设置成null,此时刚才创建的对象就不可达了,因为没有句柄再指向它,
// 对象此时状态是(unreachable,unfinalized)
SAVE_HOOK = null;
// 强制系统执行垃圾回收,系统发现刚才创建的对象处于unreachable状态,
// 并检测到这个对象的类覆盖了finalize方法,因此把这个对象放入F-Queue队列,
// 由低优先级线程执行它的finalize方法,
// 此时对象的状态变成(finalize-reachable,finalizable)
System.gc();
// sleep,目的是给低优先级线程从F-Queue队列取出对象并执行其finalize方法提供机会。
// 在执行完对象的finalize方法中的super.finalize()时
// 对象的状态变成(unreachable,finalized)状态,
// 但接下来在finalize方法中又执行了SAVE_HOOK = this;
// 又有句柄指向这个对象,对象又可达了,也就是复活。
// 因此对象的状态又变成了(reachable, finalized)状态。
Thread.sleep(500);
if (null != SAVE_HOOK) {
// 这句话会输出,注意对象由unreachable,经过finalize复活,又变成reachable。
System.out.println("Yes , I am still alive");
} else {
System.out.println("No , I am dead");
}
// 再一次将SAVE_HOOK放空,此时刚才复活的对象,状态变成(unreachable,finalized)
SAVE_HOOK = null;
// 再一次强制系统回收垃圾,此时系统发现对象不可达,虽然覆盖了finalize方法,
// 但已经执行过了,因此直接回收。
System.gc();
// 为系统回收垃圾提供机会
Thread.sleep(500);
if (null != SAVE_HOOK) {
// 这句话不会输出,因为对象已经彻底消失了。
System.out.println("Yes , I am still alive");
} else {
System.out.println("No , I am dead");
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("execute method finalize()");
// 注意这句话,让对象的状态由unreachable变成reachable,就是对象复活
SAVE_HOOK = this;
}
}
代码参考自:https://www.cnblogs.com/Smina/p/7189427.html
2.2.finally机制
示例代码如下:
public class Circle {
private int res = 0;
void mallocRes(int x) {
res = x;
System.out.println("Malloc resource:" + res);
}
void cleanRes() {
System.out.println("Clean resource:" + res);
res = 0;
}
void doSomething() {
System.out.println("Do something");
}
public static void main(String[] args) {
Circle test = new Circle();
try {
test.mallocRes(5);
test.doSomething();
// 其它代码
} finally {
test.cleanRes();
}
}
}
首先将分配资源的方法放在try块内,本例中用test.mallocRes(5)表示,将释放资源的方法放在try块后边的finally块中。这样当代码的执行离开try块时,无论发生何种情况,比如各种异常等,finally块中的test.cleanRes()都会被执行,从而实现资源的及时释放,并且一定会被释放。Java中的finally就相当于C++中的析构函数。