Java的垃圾回收机制浅析

《Java编程思想》中关于Java的垃圾回收机制有这样三句话:

1. 对象可能不会被垃圾回收

2. 垃圾回收并不等于析构

3. 垃圾回收只与内存有关

 

一 垃圾回收机制的理解

为了能够理解这几句话,写个小例子来尝试一下。

class Game {
    Game(int i) {
        System.out.println("Game Constructor + " + i);
    }
    
    public void finalize() {
        System.out.println("Game Destructor");
    }
}

class BoardGame extends Game {
    BoardGame(int i) {
        super(i);
        System.out.println("BoardGame Contructor + " + i);
    }
    
    public void finalize() {
        System.out.println("BoardGame Destructor");
    }
}

public class Chess extends BoardGame {
    private static int CHESS_SUM = 16;
    private int number;
    public Chess(int i) {
        super(i);
        number = i;
        System.out.println("Chess Contructor + " + i);
    }
    
    public void finalize() {
        System.out.println("Chess Destructor");
        
        if (number <= CHESS_SUM) {
            System.out.println("Some Chesses were lost!");
        }
    }
    
    public static void main(String[] args) {
        Chess chess = new Chess(15);
        
        chess = null;
        
        System.gc();
    }
}

这个例子里面有几个类,Game, GameBoard, Chess。Chess继承自GameBoard,GameBoard继承自Game。每个类都有重写构造函数,并且写了finalize函数。

执行结果有三种情况,都有可能出现:

第一种:

Game Constructor + 15
BoardGame Contructor + 15
Chess Contructor + 15

第二种:

Game Constructor + 15
BoardGame Contructor + 15
Chess Contructor + 15
Chess Destructor

第三种:

Game Constructor + 15
BoardGame Contructor + 15
Chess Contructor + 15
Chess Destructor
Some Chesses were lost!

这样三种情形都有可能出现就很有趣了,不过相同的是构造的过程。当实例化一个Chess类的时候,构造方法依次从基类到子类顺序执行执行。

但是垃圾回收的过程就非常不同了。第一种情况下,finalize函数根本没有执行。可能的原因有两个:JVM没有执行垃圾回收;垃圾回收时未执行finalize函数。两个原因都有可能,具体是什么原因就只有JVM自己知道了。为什么会未执行垃圾回收,这是因为JVM认为当前的内存足够,没有必要执行垃圾回收。即便是调用了System.gc(),垃圾回收也有可能不会立即执行。这是因为垃圾回收本就是一个十分耗费资源的操作,不必要执行的时候还是不要执行。只有当JVM所剩的内存不多的时候,JVM才会自动执行垃圾回收。这就是JVM垃圾回收机制的不确定性。

第二种情况中,垃圾回收过程确实的执行了,在这个过程中,也确实的执行了finalize。但是这个方法并未被执行完整,因为后面的判断未被执行。

第三种情况中,垃圾回收过程被执行了,而且finalize也被完整执行了。

通过上面的例子,对于JVM的垃圾回收机制开始有些理解了。结合这个例子,谈一下上面三句话的理解。

1. 对象可能不会被垃圾回收。垃圾回收机制是由JVM来控制的,Java的设计者们希望Java程序猿不要关心内存管理,而是由JVM来执行内存的清理。所以当对象指向null的时候,这个对象也可能不会被垃圾回收。因为JVM觉得当前内存足够,不需要浪费资源执行垃圾回收的操作。

2. 垃圾回收并不等于析构。Java并未提供“析构函数”的概念,他认为一个对象在消失之前所执行的操作应该被显示的表达出来。所以即便是提供了finalize方法,JVM也没有保证在垃圾回收时执行该方法。所以垃圾回收并不是一个析构的过程。

3. 垃圾回收只与内存有关。垃圾回收的工作仅仅是将无主的内存空间释放出来,并不负责对象被回收之前的判定。这也就是为什么上述例子中,即便是finalize方法被执行了,执行结果也不相同了。垃圾回收并不保证finalize被完整的执行,只是把chess对象所占用的内存释放出来。

 

二 关于finalize方法的使用

理解了上述三句话之后,我又会产生一个疑问。既然finalize不保证被完整执行,甚至不保证被执行,那么为什么要设计这个方法呢?通过查询其他朋友的博客,又增加了一些理解。

1. finalize一定会被执行。

2. 具体什么时间执行要看JVM的调度。

3. 最重要的,尽量不要用finalize。

即便如此,finalize依然有存在的必要,可以用来保护非内存资源被释放。垃圾回收器只能负责收集堆上分配的内存,对于栈上分配的内存,垃圾收集器就管不着了。比如当使用JNI技术时,可能会在栈上分配内存,例如java调用c程序,而该c程序使用malloc分配内存时。这样的情况下,使用finalize就很有必要了。

当然,释放非内存资源最有效的方法是显示的调用finalize方法,这时该方法跟普通的方法调用没区别。如果给别的程序员调用的时候,如果他没有显示的调用finalize方法,JVM在垃圾回收的时候依然会释放这部分非内存资源。毕竟,晚释放总比不释放好,至少保证了即便是使用了JNI技术调用C程序,也不会导致内存泄露。

 

三 如何保证正确的清理

Java中没有析构的概念,Java程序猿需要的是忘记对象,而不是销毁他,让垃圾回收器自行清理内存。这样做对程序员来说十分方便,但是有时也需要一些必要的清理。我们不知道垃圾回收器什么时候会执行,或者是否会执行。因此想要清理一些对象的时候,必须要显式地编写和执行清理方法来做这些事,并且要让使用这个类的同事指导并调用这个方法。这个清理方法必须置于finally子句中,以防止异常导致清理方法不被执行。

大多数情况下,让垃圾回收器完成垃圾清理就行了。当必须自己清理的时候就得多做努力并多加小心。因为一旦涉及到垃圾回收,可以信赖的事情就不多了。垃圾回收可能永远不被调用,即使被调用,它也会用它想要的顺序来回收对象。所以最好的方法是除了内存以外,不能依赖垃圾回收器做任何事。如果要清理,就自己定义清理方法,不要使用finalize。

修改上面的例子:

class Game {
    Game(int i) {
        System.out.println("Game Constructor + " + i);
    }
    
    public void dispose() {
        System.out.println("Game Destructor");
    }
}

class BoardGame extends Game {
    BoardGame(int i) {
        super(i);
        System.out.println("BoardGame Contructor + " + i);
    }
    
    public void dispose() {
        System.out.println("BoardGame Destructor");
        super.dispose();
    }
}

public class Chess extends BoardGame {
    private static int CHESS_SUM = 32;
    private int number;
    public Chess(int i) {
        super(i);
        number = i;
        System.out.println("Chess Contructor + " + i);
    }
    
    public void setNumber(int i) {
        this.number = i;
    }
    
    public void dispose() {
        System.out.println("Chess Destructor");
        
        if (number <= CHESS_SUM) {
            System.out.println("Some Chesses were lost!");
        }
        super.dispose();
    }
    
    public static void main(String[] args) {
        Chess chess = new Chess(30);
        
        try {
            chess.setNumber(31);
        } catch (Exception e) {
            
        } finally {
            chess.dispose();
        }
        
        System.gc();
        
    }
}

执行结果:

Game Constructor + 30
BoardGame Contructor + 30
Chess Contructor + 30
Chess Destructor
Some Chesses were lost!
BoardGame Destructor
Game Destructor

 

参考:

深入理解java的finalize,http://www.iteye.com/topic/484934

posted on 2013-08-22 11:55  洪雁君  阅读(651)  评论(0编辑  收藏  举报

导航