虽然当我们用完对象后,java有垃圾回收机制进行回收,但是并没有那么的智能,对于某些被引用的对象,就算我们已经不在使用它了,但是java的回收机制是不会回收他们的,人们称之为“内存泄漏”。
很多时候内存泄漏都是“人们无意识的内存引用”造成的,举个简单的例子:
List<String> list = new ArrayList<>(); String str = "testString"; list.add(str); str = null;
看似上面的str被回收了,但是,事实上创建str时所开辟的内存空间是不会被回收的,因为list依然持有对str的引用这就是一个典型的“无意识的内存引用”。为了防止这些“无意识的内存引用”,我们应该了解对象相互引用的时候是存在怎样的依赖关系的。
下面我们在看看一个简单的栈实现例子:
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; } private void ensureCapacity(){ if(elements.length == size){ elements = Arrays.copyOf(elements,2*size+1); } } public Object pop(){ if(size == 0){ throw new EmptyStackException(); } Object result = elements[--size]; return result; } }
看似这段程序没有明显的错误,也能运行,但是这里面存在一个内存泄漏问题,如果栈先增长了,然后在收缩,那么栈中弹出的对象将不被当做垃圾回收,即使使用栈的程序不在引用这些对象了,它们也不会被回收,因为栈内部维护着对这些对象的过期引用,所谓的过期引用是指永远不在会被解除的引用,这里就是凡在elements数组的活动部分之外的任何引用都是过期的。
对于上述这些修复的方法还是比较简单的,一旦对象是过期引用,只需要清空这些引用即可,对应上述例子中的Stack而已,只要一个单元被弹出栈,那就是过期引用,只需要在pop方法的return result上面加上elements[size] = null;即可。
当然,我们不要因为害怕内存泄漏而在所有的地方都手动回收内存,这样会导致我们的代码凌乱臃肿,不利于管理,我们应该把目标集中在那些长声明周期的变量中,哪些是长生命周期的变量呢,最明显的一个就是static修饰的变量,我们应该把目光放在这些变量上。