尽管Java不像C/C++那样需要手工管理内存资源,而是通过更为方便、更为智能的垃圾回收机制来帮助开发者清理过期的资源。即便如此,内存泄露问题仍然会发生在你的程序中,只是和C/C++相比,Java中内存泄露更加隐匿,更加难以发现,见如下代码:



[java]​view plain ​​​​copy​



  1. // Can you spot the "memory leak"?  

  2. import java.util.*;  

  3. public class Stack {  
  4.     private Object[] elements;  
  5.     private int size = 0;  
  6.     private static final int DEFAULT_INITIAL_CAPACITY = 16;  

  7.     public Stack() {  
  8.         elements = new Object[DEFAULT_INITIAL_CAPACITY];  
  9.     }  

  10.     public void push(Object e) {  
  11.         ensureCapacity();  
  12.         elements[size++] = e;  
  13.     }  

  14.     public Object pop() {  
  15.         if (size == 0)  
  16.             throw new EmptyStackException();  
  17.         return elements[--size];  
  18.     }  

  19.     /** 
  20.      * Ensure space for at least one more element, roughly 
  21.      * doubling the capacity each time the array needs to grow. 
  22.      */  
  23.     private void ensureCapacity() {  
  24.         if (elements.length == size)  
  25.             elements = Arrays.copyOf(elements, 2 * size + 1);  
  26.     }  
  27. }  

以上示例代码,在正常的使用中不会产生任何逻辑问题,然而随着程序运行时间不断加长,内存泄露造成的副作用将会慢慢的显现出来,如磁盘页交换、OutOfMemoryError等。那么内存泄露隐藏在程序中的什么地方呢?当我们调用pop方法是,该方法将返回当前栈顶的elements,同时将该栈的活动区间(size)减一,然而此时被弹出的Object仍然保持至少两处引用,一个是返回的对象,另一个则是该返回对象在elements数组中原有栈顶位置的引用。这样即便外部对象在使用之后不再引用该Object,那么它仍然不会被垃圾收集器释放,久而久之导致了更多类似对象的内存泄露。修改方式如下:



[java]​view plain ​​​​copy​



  1. public Object pop() {    
  2.     if (size == 0)     
  3.         throw new EmptyStackException();    
  4.     Object result = elements[--size];    
  5.     elements[size] = null;  
  6.     return result;    
  7. }   


由于现有的Java垃圾收集器已经足够只能和强大,因此没有必要对所有不在需要的对象执行obj = null的显示置空操作,这样反而会给程序代码的阅读带来不必要的麻烦,该条目只是推荐在以下3中情形下需要考虑资源手工处理问题:

1) 类是自己管理内存,如例子中的Stack类。

2) 使用对象缓存机制时,需要考虑被从缓存中换出的对象,或是长期不会被访问到的对象。

3) 事件监听器和相关回调。用户经常会在需要时显示的注册,然而却经常会忘记在不用的时候注销这些回调接口实现类。