垃圾收集器只知道释放那些有new分配的内存,所以不知道如何释放对象的“特殊”内存。为了解决这个问题,Java提供了一个名为finalize()的方法,可为我们的类定义它。在理想的情况下,它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。(PS:在一些面试中,面试官会问GC在回收一个对象时是否会马上释放掉对象的内存?答案就在此)。
注意点:1 垃圾收集并不等于“破坏”!
它意味着在我们不再需要一个对象之前,有些行动是必须采取的,而且必须由自己来采取这些行动。Java并未提供“破坏器”或者类似的概念,所以必须创建一个原始的方法,用它来进行这种清楚。例如,假设在对象创建过程中,它会将自己描绘到屏幕上。如果不从屏幕明确删除它的图像,那么它可能永远都不会被清楚。若在finalize()里置入某种删除机制,那么假设对象被当做垃圾收掉了,图像首先会将自身从屏幕上移去。但若未被收掉,图像就会保留下来。这里就涉及到了对象的在回收前finalize()的用法。
2 我们的对象可能不会当做垃圾被收掉!
有时可能发现一个对象的存储空间永远都不会释放,因为自己的程序永远都接近于用光空间的临界点。若程序执行借宿,而且垃圾收集器一直都没有释放我们创建的任何对象的存储空间,则随着程序的推出,那些资源会返回给操作系统。这是一件好事,因为垃圾收集本身也要消耗一些开销。如永远都不用它,那么永远也不用支出这部分开销。
finalize()的用途:
一、垃圾收集只跟内存有关。即垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。因此finalize()方法必须同内存以及对象的回收有关。GC将finalize()的需求限制到特殊的情况,个人理解为,在对象正式回收前对该对象做一些操作,比如控制台打印该对象即将被回收等信息。之所以要使用finalize(),看起来似乎是由于有时需要采取与Java的普通方法不同的一种方法,通过分配内存来做一些具有C风格的事情。这主要是通过“固有方法”来进行,它是从Java里调用非Java方法的一种方式。C和C++是目前唯一获得固有方法支持的语言。finalize()内部的一个固有方法中调用了C和C++函数中的free()方法来使得存储空间得到释放,从而造成内存“漏洞”的出现。
二、finalize()不必过多地使用,因为它并不是进行普通清除工作的理想场所。
三、finalize()最有用处的地方之一是观察垃圾收集的过程。绝对不能直接调用finalize(),所以应尽量避免用它。
如下的Demo将展示垃圾收集的详细过程:
import java.util.Scanner;
//: Garbage.java
// Demonstration of the garbage
// collector and finalization
class Chair {
static boolean gcrun = false;static boolean f = false;
static int created = 0;
static int finalized = 0;
int i;
Chair() {
i = ++created;
System.out.println(created);
if (created == 47){
System.out.println("Created 47");
// f=true;
}
}//垃圾回收前,GC自动调用,当回收47号对象时,会打印一句话
protected void finalize() {
if (!gcrun) {
gcrun = true;
System.out.println("Beginning to finalize after " + created
+ " Chairs have been created");
}
if (i == 47) {
System.out.println("Finalizing Chair #47, "
+ "Setting flag to stop Chair creation");
f = true;
}
finalized++;
if (finalized >= created)
System.out.println("All " + finalized + " finalized");
}
}public class Garbage {
public static void main(String[] args) {
/*if (args.length == 0) {
System.err.println("Usage: \n" + "java Garbage before\n or:\n"
+ "java Garbage after");
return;
}*/
Scanner sc=new Scanner(System.in);
System.out.println("Please input a word>>before or after:");
String str=sc.next();
while (!Chair.f) {
new Chair();
new String("To take up space");
}
System.out.println("After all Chairs have been created:\n"
+ "total created = " + Chair.created + ", total finalized = "
+ Chair.finalized);
if (str.equals("before")) {
System.out.println("gc():");
System.gc();
System.out.println("runFinalization():");
System.runFinalization();
}
System.out.println("bye!");
if (str.equals("after"))
System.runFinalizersOnExit(true);
}
}上面这个程序创建了许多Chair 对象,而且在垃圾收集器开始运行后的某些时候,程序会停止创建Chair。
由于垃圾收集器可能在任何时间运行,所以我们不能准确知道它在何时启动。因此,程序用一个名为gcrun
的标记来指出垃圾收集器是否已经开始运行。利用第二个标记f,Chair 可告诉main()它应停止对象的生
成。这两个标记都是在finalize()内部设置的,它调用于垃圾收集期间。
另两个static 变量——created 以及finalized——分别用于跟踪已创建的对象数量以及垃圾收集器已进行
完收尾工作的对象数量。最后,每个Chair 都有它自己的(非static)int i,所以能跟踪了解它具体的编
号是多少。编号为47 的Chair 进行完收尾工作后,标记会设为true ,最终结束Chair 对象的创建过程。
所有这些都在main()的内部进行——在下面这个循环里:
while(!Chair.f) {
new Chair();
new String("To take up space");
}
大家可能会疑惑这个循环什么时候会停下来,因为内部没有任何改变Chair.f 值的语句。然而,finalize()
进程会改变这个值,直至最终对编号47 的对象进行收尾处理。
每次循环过程中创建的String 对象只是属于额外的垃圾,用于吸引垃圾收集器——一旦垃圾收集器对可用内
存的容量感到“紧张不安”,就会开始关注它。
因此,到程序结束的时候,并非所有收尾模块都会得到调用。为强制进行收尾工作,可先调用
System.gc(),再调用System.runFinalization()。这样可清除到目前为止没有使用的所有对象。这样做一
个稍显奇怪的地方是在调用runFinalization()之前调用gc(),这看起来似乎与Sun 公司的文档说明有些抵
触,它宣称首先运行收尾模块,再释放存储空间。然而,若在这里首先调用runFinalization(),再调用
gc(),收尾模块根本不会执行。
(备注:以上学习笔记源自《Thinking in Java》)