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