所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。
下面列出常见的内存泄漏问题产生原因:
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象(内部类对象都有外部类对象的引用,可以用于方便地访问外部类的成员和方法),这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
基于数组的堆栈内存泄漏问题。主要特点就是清空堆栈中的某个元素,并不是彻底把它从数组中拿掉,而是把存储的总数减少,本人写得可以比这个好,在拿掉某个元素时,顺便也让它从数组中消失,将那个元素所在的位置的值设置为null即可。
public class Stack { private Object[] elements=new Object[10]; private int size = 0; public void push(Object e){ ensureCapacity(); elements[size++] = e; } public Object pop(){ if( size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity(){ if(elements.length == size){ Object[]oldElements = elements; elements = newObject[2 * elements.length+1]; System.arraycopy(oldElements,0, elements, 0, size); } } }
上面的原理应该很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,没有我们要的东西,但是这是个对象是无法回收的,因为数组中对堆栈元素的引用依然存在,也就是数组对象持有了堆栈中元素的引用,但是这些对象实际上已经没用了,却无法回收。这个才符合了内存泄露的两个条件:无用,无法回收。
下面是对堆栈的改进,消除了内存泄漏的问题。
class MNode<T> { private T value = null; private MNode<T> next; public MNode(T value) { this.value = value; next = null; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } public MNode<T> getNext() { return next; } public void setNext(MNode<T> first) { this.next = first; } } public class MyBetterLinkedStack<T> { private MNode<T> first; private int size; public MyBetterLinkedStack() { this.first = null; this.size = 0; } @SuppressWarnings("unchecked") public <T> boolean push(T t) { if(t==null) throw new RuntimeException("不能传入空值"); MNode newNode = new MNode<T>(t); newNode.setNext(first); first = newNode; size++; return true; } @SuppressWarnings("unchecked") public <T> T pop() { if(size==0) return null; MNode r = first; first = first.getNext(); size--; T result = (T) r.getValue(); r = null; return result; } public int getSize() { return this.size; } public boolean isEmpty() { return size == 0; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generatedmethod stub MyBetterLinkedStack mbls = new MyBetterLinkedStack(); mbls.push("haha"); mbls.push("hehe"); System.out.println(mbls.getSize()); System.out.println(mbls.pop()); System.out.println(mbls.pop()); } }
内存泄露的另外一种情况:当一个对象被存储进HashSet(HashSet是基于HashMap的)集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。