为了直观,我们先上代码,哈哈哈哈哈:
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) { ensureCapacity(); } return elements[--size]; } private void ensureCapacity(){ if(elements.length==size){ elements = Arrays.copyOf(elements,2*size + 1); } } }
上面代码没有明显的错误,也许数据量小的话,根本就不会出现什么问题。
但是代码里隐式的存在一个内存泄漏,随着数据量的不断增加,程序越来越慢,最后就会发生内存泄漏,
那内存泄漏发生在哪里呢?
如果栈先是增长,然后再收缩,那么从栈中弹出来的对象将不会当作垃圾回收,即使栈不在引用这些对象。
因为栈内部还维护者对这些对象的过期引用,所以垃圾回收期永远也不会去回收那些没有用的数据,随着时间越来越久,
总有一天会发生内存泄漏,解决办法,就是修改pop方法:
public Object pop() { if (size == 0) { ensureCapacity(); } Object result = elements[--size]; elements[size] = null; return result; }
将栈中取出的对象置为null,这样GC回收器就会去回收过期对象。
内存泄漏的另外一个常见来源是缓存,一旦你把对象引用存放在缓存中,它就很容易被遗忘掉,从而之后的很长一段时间任然留在缓存中。如果你要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义,那么可以使用WeakHashMap代表缓存,当缓存中的项过期后,他们会自动删除。记住只有当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。
我们的缓存随着时间的推移,越早加入的缓存数据,会变得越来越没有价值,在这种情况下,缓存应该时不时的清除掉没用的项,这种清除工作可以由一个后台线程(Timer,quartz或者ScheduledThrealPoolExecutor)来完成,或者也可以在给缓存添加新条目的时候顺便进行清理,LinkedHashMap可以利用它的removeEldestEntry方法可以很容易的实现后一种方案,对于更复杂的缓存可以直接使用java.lang.ref。
内存泄漏的第三个常见的来源的是监听器和其他回调。可以借助Heap剖析工具Heap Profiler 来查看内存泄漏的情况。