虽然在Java语言中拥有垃圾收集(GC)回收程序,在创建对象后,不用程序员手动回收对象,但在某些情况下依然会造成内存泄漏的情况。
在支持垃圾回收的语言中,内存泄漏是很隐蔽,也可以称这类内存泄漏为“无意识的对象保留”更为恰当。如果一个对象引用被无意识地保留起来,那么来及回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象,即使只有少量的几个对象引用被无意识地保留下来,也会有许许多多的对象呗排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。
内存泄漏主要有如下三种方式:
1. 过期的对象引用引起的内存泄漏
示例代码如下:
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object obj) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if(size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
/**
* Ensure space for at least one more element, roughly
* doubling tha capacity each time the array needs to grow.
*/
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
分析:如果一个栈先增长,然后在收缩,那么,从栈中弹出来的对象将不会被当作垃圾回收,即使使用栈的程序不在引用这些对象,它们也不会被回收,因为栈内部维护着对这些对象的过期引用(obsolete reference)。
过期引用:永远不会再被解除的引用,在上面代码中,凡是在elements数组的“活动部分(active portion)”之外的任何引用都是过期的。活动部分是指elements中下标小于size的那些元素。
解决办法:如下:一旦对象的引用已过期,清空这些引用。这样的一个好处是,如果它们以后又被错误的引用,程序就会立即抛出NullPointerException
异常(尽快地检测出程序中的错误总是有益的)。
public Object pop() {
if(size == 0) {
throw new EmptyStackException();
}
Object res = elements[--size];
elements[size] = null; //Eliminate obsolete reference
return res;
}
注:清空对象引用应该是一种例外,而不是一种规范行为。
- 消除过期引用的最好的办法是让包含该引用的变量结束其生命周期,即为变量定义最紧凑的作用域。
- 只要类是自己管理内存,程序员就应该警惕内存泄漏的问题。
2. 缓存中的对象引起的内存泄漏
内存泄漏的另一个常见的来源是缓存:对象应用存放在缓存中,当对象不再被使用时,很容易被遗忘掉而没有清理,于是,该对象引用会一直保存在缓存中,而你在逻辑上已经没有使用该对象,但该对象不会被GC回收,因为仍然有引用指向它。
解决办法:知道什么时候,缓存中的引用对象不再有用,有意义,在这个时候,就可以清理掉缓存中的对象引用。
当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时,可以采用WeakHashMap代表缓存;
(代码示例,待续···)更为常见的情形是“缓存项的生命周期是否有意义”并不是可容易确定,随着时间的推移,其中的项会变得越来越没有价值,在这种情况下,缓存应该时不时的清楚掉没用的项,可采用后台进程(Timer或者ScheduledThreadPoolExecutor)来完成,也可以在给缓存添加新条目的时候顺便进行清理(LinkedHashMap类中的removeEldestEntry方法容易实现该方案);对于更复杂的缓存,必须直接使用java.lang.ref。
(代码示例,待续···)
3. 监听器和其他回调引起的内存泄漏
内存泄漏的第三个常见来源是监听器和其他回调:我们实现了一个API,用户在这个API中注册了回调,却没有显示地曲线注册,除非采取某些方法,否则他们就会积聚,确保回调立即被当作垃圾回收的最佳方法就是保存它的弱引用(weak reference),如:只把它们保存成WeakHashMap中的键。
(代码示例,待续···)