UML软件工程组织

深入浅出谈垃圾的回收—Java 堆的管理 (3)
来自:http://www.javaresearch.org



class chair {
 static boolean gcrun = false;
 static boolean f = false;
 static int created = 0;
 static int finalized = 0;
 int i;
 chair() {
  i = created;
  if(created == 47) 
   system.out.println(created 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 beforen or:n Java garbage after);
   return;
  }
  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(args[0].equals(before)) {
    system.out.println(gc():);
    system.gc();
    system.out.println(runfinalization():);
    system.runfinalization();
  }
  system.out.println(bye!);
  if(args[0].equals(after))
   system.runfinalizersonexit(true);
 }
}



上面这个程序创建了许多chair对象,而且在垃圾收集器开始运行后的某些时候,程序会停止创建chair。由于垃圾收集器可能在任何时间运行,所以我们不能准确知道它在何时启动。因此,程序用一个名为gcrun的标记来指出垃圾收集器是否已经开始运行。利用第二个标记f,chair可告诉main()它应停止对象的生成。

这两个标记都是在finalize()内部设置的,它调用于垃圾收集期间。另两个static变量created以及finalized分别用于跟踪已创建的对象数量以及垃圾收集器已进行完收尾工作的对象数量。最后,每个chair都有它自己的(非static)int i,所以能跟踪了解它具体的编号是多少。编号为47的chair进行完收尾工作后,标记会设为true,最终结束chair对象的创建过程。

关于垃圾收集的几点补充

经过上述的说明,可以发现垃圾回收有以下的几个特点:

(1)垃圾收集发生的不可预知性:

由于实现了不同的垃圾收集算法和采用了不同的收集机制,所以它有可能是定时发生,有可能是当出现系统空闲cpu资源时发生,也有可能是和原始的垃圾收集一样,等到内存消耗出现极限时发生,这与垃圾收集器的选择和具体的设置都有关系。

(2)垃圾收集的精确性:

主要包括2个方面:(a)垃圾收集器能够精确标记活着的对象;(b)垃圾收集器能够精确地定位对象之间的引用关系。前者是完全地回收所有废弃对象的前提,否则就可能造成内存泄漏。而后者则是实现归并和复制等算法的必要条件。所有不可达对象都能够可靠地得到回收,所有对象都能够重新分配,允许对象的复制和对象内存的缩并,这样就有效地防止内存的支离破碎。 (3)现在有许多种不同的垃圾收集器,每种有其算法且其表现各异,既有当垃圾收集开始时就停止应用程序的运行,又有当垃圾收集开始时也允许应用程序的线程运行,还有在同一时间垃圾收集多线程运行。

(4)垃圾收集的实现和具体的Jvm 以及Jvm的内存模型有非常紧密的关系。不同的Jvm 可能采用不同的垃圾收集,而Jvm 的内存模型决定着该Jvm可以采用哪些类型垃圾收集。现在,hotspot 系列Jvm中的内存系统都采用先进的面向对象的框架设计,这使得该系列Jvm都可以采用最先进的垃圾收集。

(5)随着技术的发展,现代垃圾收集技术提供许多可选的垃圾收集器,而且在配置每种收集器的时候又可以设置不同的参数,这就使得根据不同的应用环境获得最优的应用性能成为可能。

针对以上特点,我们在使用的时候要注意:

(1)不要试图去假定垃圾收集发生的时间,这一切都是未知的。比如,方法中的一个临时对象在方法调用完毕后就变成了无用对象,这个时候它的内存就可以被释放。

(2)Java中提供了一些和垃圾收集打交道的类,而且提供了一种强行执行垃圾收集的方法调用system.gc(),但这同样是个不确定的方法。Java 中并不保证每次调用该方法就一定能够启动垃圾收集,它只不过会向Jvm发出这样一个申请,到底是否真正执行垃圾收集,一切都是个未知数。

(3)挑选适合自己的垃圾收集器。一般来说,如果系统没有特殊和苛刻的性能要求,可以采用Jvm的缺省选项。否则可以考虑使用有针对性的垃圾收集器,比如增量收集器就比较适合实时性要求较高的系统之中。系统具有较高的配置,有比较多的闲置资源,可以考虑使用并行标记/清除收集器。

(4)关键的也是难把握的问题是内存泄漏。良好的编程习惯和严谨的编程态度永远是最重要的,不要让自己的一个小错误导致内存出现大漏洞。

(5)尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null,暗示垃圾收集器来收集该对象,还必须注意该引用的对象是否被监听,如果有,则要去掉监听器,然后再赋空值。

一般来说,Java开发人员可以不重视Jvm中堆内存的分配和垃圾处理收集,但是,充分理解Java的这一特性可以让我们更有效地利用资源。同时要注意finalize()方法是Java的缺省机制,有时为确保对象资源的明确释放,可以编写自己的finalize方法。

 

 

版权所有:UML软件工程组织