×

消息

EU e-Privacy Directive

This website uses cookies to manage authentication, navigation, and other functions. By using our website, you agree that we can place these types of cookies on your device.

View e-Privacy Directive Documents

You have declined cookies. This decision can be reversed.

移除过期的对象引用

这节讲的是关于内存泄露的问题。Java有三大主要的内存泄露来源:

1.开发者自己维护的内存

2.缓存

3.listener和callback

第一类:开发者自己维护的内存。这类问题通常是开发者从其它编程语言(例如c++)带来的坏习惯导致,比如下面这段代码:

// Can you spot the "memory leak"?
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 e) {
		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 the capacity each time the array needs to grow.
	*/
	private void ensureCapacity() {
		if (elements.length == size)
			elements = Arrays.copyOf(elements, 2 * size + 1);
		}
}

这个栈的问题就在pop()方法:每弹出一个元素,栈指针下移一格,但这个指针是由开发者维护,jvm在技术层面对它一无所知。开发者在指针下移后知道,高于指针位置的元素不再有用,但jvm不知道这些。对于它来说,elements数组的容量没有变化,因而所有elements中的元素都仍有引用存在,不会被GC处理,最终已经弹出的元素所占用的内存不会被释放。解决方法是手动移除这些引用:

public Object pop() {
	if (size == 0)
		throw new EmptyStackException();
	Object result = elements[--size];
	elements[size] = null; // Eliminate obsolete reference
	return result;
}

这样处理的另一个好处是,一旦这段代码被错误调用,由于有元素被错误清除,会很快有NPE弹出,而不是使程序错误地执行下去。同时要注意,这种手动移除引用的方法不可滥用,只能作为处理这类特殊情况的办法。正常情况下,应该依赖变量的作用域来自动处理引用,即只在绝对必要的最小范围内定义变量。

第二类:缓存,即cache

比较简单的情况,是缓存元素的生命周期只依赖于缓存外对它的引用,一旦不再有外部引用,元素就可以销毁。这时只要用WeakHashMap来实现缓存即可。另一种情况是缓存元素有其自己的对象生命周期,这时就需要一个管理线程来定期清理缓存了,比较有用的工具是Timer,ScheduledThreadPoolExecutor,还有LinkedHashMap的removeEldestEntry方法。

第三类内存泄露来源是listener和各种callback

主要是确保deregister和register匹配,还有尽可能只存储callback的弱引用(weak reference),比如把它们作为key存储在WeakHashMap中。

提交评论


安全码
刷新