--Java中会存在内存泄露吗?
--如果你想当然的以为Java里有了垃圾回收机制就不会存在内存泄露,那你就错了。
Java里也会存在内存泄露!
我们慢慢来分析。
所谓内存泄露就是指一个不再被程序使用的对象一直占据在内存中。Java中有内存回收机制,他可以保证一对象不再被引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收机制从内存中清楚掉。
由于Java使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,即例如有两个对象相互引用,只要他们和根进程不可达,那么GC也会回收他们的。
关于Java中出现的内存泄露的情况:
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有他的引用而导致不能被回收,这就是Java中内存泄露发生的场景。
通俗的说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收。
例如缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用他,这个对象会一直被缓存引用,但却不再被使用。
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,也会造成内存泄露。
下面是一个堆栈的经典的例子:
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 = new Object[2 * elements.length + 1]; System.arraycopy(oldElements, 0, elements, 0, size); } } }
上面的代码很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,没有我们要的东西,但是这个对象是无法回收的。
这个才符合了内存泄露的两个条件:无用,无法回收。
但是,存在这样的东西也不一定会导致什么样的后果,如果这个堆栈用的比较少,也就浪费了几个K的内存而已,反正我们的内存都上G了,哪有会有什么影响。再说这个东西很快就会被回收的,关系不大。
再看下面这个例子:
class Bad{ public static Stack s = new Stack(); static{ s.push(new Object()); s.pop(); //这里有一个对象发生内存泄露了! s.push(new Object()); //上面的对象可以被回收了,等于是自愈了。 } }
因为是static,就一直存在到程序退出,但是我们也可以看到他有自愈功能,就是说如果你的Stack最多有100个对象,那么最后也就只有100个对象无法被回收。Stack持有100个引用,最坏的情况下就是他们都是无用的,因为我们一旦放进新的,以前的引用自然消失了。
内存泄露的另外一种情况:
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算hash值的字段了,否则,对象修改后的hash值与最初存储进HashSet集合中时的hash值就不同了,在这种情况下,即使contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。