垃圾回收
在Java中,当一个对象成为垃圾后仍会占用内存空间,时间一长,就会导致内存空间的不足。针对这种情况,Java中引入了垃圾回收机制(Java GC)。有了这种机制,程序员不需要过多关心垃圾对象回收的问题,Java虚拟机会自动回收垃圾对象所占用的内存空间。
当一个对象在堆内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种:
● 可用状态:当一个对象被创建后,如果有一个以上的引用变量引用它,那么这个对象在程序中将处于可用状态,程序可以通过引用变量来调用该对象的实例变量和方法。
● 可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理。如果系统在调用finalize()方法前重新使一个引用变量引用该对象,则这个对象会再次变为可用状态;否则该对象将进入不可用状态。
● 不可用状态:当对象失去了所有引用变量的关联,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可用状态,那么这个对象将永久的失去引用,变成不可用状态。只有当一个对象处于不可用状态时,系统才会真正的回收该对象所占用的内存空间。
上述三种状态的转换示意图如1所示。
图1 对象的状态转换
一个对象在彻底失去引用成为垃圾后会暂时地保留在内存中,当这样的垃圾堆积到一定程度时,Java虚拟机就会启动垃圾回收器将这些垃圾对象从内存中释放,从而使程序获得更多可用的内存空间。虽然通过程序可以控制一个对象何时不再被任何引用变量所引用,但是却无法精确的控制Java垃圾回收的时机。除了等待Java虚拟机进行自动垃圾回收外,还可以通过如下两种方式强制系统进行垃圾回收。
● 调用System类的gc()静态方法:System.gc()。
● 调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()。
实际上,调用System.gc()方法时,所执行的也是Runtime.getRuntime().gc()方法。需要说明的是,调用这两种方式可以强制启动垃圾回收器进行垃圾回收,但系统是否立即进行垃圾回收依然具有不确定性。大多数情况下,强制系统垃圾回收后总是有一定的效果。
当一个对象在内存中被释放时,它的finalize()方法会被自动调用,finalize()方法是定义在Object类中的实例方法,其方法原型如下:
protected void finalize() throws Throwable { }
任何Java类都可以重写Object类的finalize()方法,在该方法中清理该对象占用的资源。如果程序终止之前仍然没有进行垃圾回收,则不会调用失去引用对象的finalize()方法来清理资源。
需要注意的是,只有当程序认为需要更多的额外内存时,垃圾回收器才会自动进行垃圾回收,而在一些情况下,对象的finalize()方法并不一定会被调用,例如某个失去了引用的对象只占用了少量的内存,而系统并没有严重的内存需求,此时垃圾回收机制就可能不会回收该对象所占用的资源,所以该对象的finalize()方法也就不会被调用。
接下来通过一个案例来演示Java虚拟机进行垃圾回收的过程,如文件1所示。
文件1 Example37.java
1 class Person {
2 // 下面定义的finalize()方法会在垃圾回收前被调用
3 public void finalize() {
4 System.out.println("对象将被作为垃圾回收...");
5 }
6 }
7 public class Example37 {
8 // 1、演示一个不通知强制垃圾回收的方法
9 public static void recyclegWaste1(){
10 Person p1 = new Person();
11 p1 = null;
12 int i = 1;
13 while (i < 10) {
14 System.out.println("方法1循环中...........");
15 i++;
16 }
17 }
18 // 2、演示一个通知强制垃圾回收的方法
19 public static void recyclegWaste2(){
20 Person p2 = new Person();
21 p2 = null;
22 // 通知垃圾回收器进行强制垃圾回收
23 System.gc();
24 // Runtime.getRuntime().gc();
25 int i = 1;
26 while (i < 10) {
27 System.out.println("方法2循环中...........");
28 i++;
29 }
30 }
31 public static void main(String[] args) {
32 // 分别调用两个模拟演示垃圾回收的方法
33 recyclegWaste1();
34 System.out.println("================");
35 recyclegWaste2();
36 }
37 }
运行结果如图2所示。
图2 运行结果
文件1中,Person类重写了finalize()方法,并在方法中编写了一条输出语句来查看垃圾回收时的执行效果。在测试类中先分别创建了两个演示垃圾回收的方法recyclegWaste1()和recyclegWaste2(),在recyclegWaste1()方法中将一个新创建的对象引用去除后并没有强制调用垃圾回收器,而在recyclegWaste2()方法中将一个新创建的对象引用去除后就立即强制调用垃圾回收器进行垃圾回收,并在两个方法中都添加了一个while循环来模拟程序中其他执行过程。最后,在main()方法中按照顺序先后调用了recyclegWaste1()和recyclegWaste2()方法。
从图2可以看出,在recyclegWaste1()方法中,一个对象失去了引用不会立即作为垃圾被回收,即使整个方法结束也一样;而在recyclegWaste2()方法中,一个对象失去了引用后,就会立即启动垃圾回收器进行垃圾回收,但这个过程也不是瞬间进行回收垃圾的,而是在程序继续执行过程中陆续将所有垃圾进行回收,包括当前recyclegWaste2()方法以及recyclegWaste1()方法产生的垃圾。
如果在文件1中的main()方法中,将调用的两个演示垃圾回收的方法recyclegWaste1()和recyclegWaste2()方法执行顺序互换,再次运行程序,结果如图3所示。
图3 运行结果
从图3可以看出,整个执行过程中只有一条“对象将被作为垃圾回收”的输出信息,说明该过程中只立即执行了一次强制垃圾回收,也就是强制回收了recyclegWaste2()方法中失去引用的垃圾对象p2。其原因是,在main()方法中先调用了recyclegWaste2()方法,在该方法中通知垃圾回收器进行强制垃圾回收时,只发现了垃圾对象p2,所以会在某个时间进行垃圾回收,而在执行了recyclegWaste1()方法时,又产生了一个垃圾对象p1,此时却没有再次通知垃圾回收器,所以不会立即进行垃圾回收。