1.什么是内存泄露
内存泄露是当某些对象不再被应用程序所使用,而垃圾回收器(GC)没有识别到这些对象不再使用(),从而这些对象无法被垃圾回收器回收,占用的内存空间无法得到释放.
程序运行过程中会不断的分配内存空间,那些不再使用的内存空间应该及时地回收,如果存在无用的内存没有被回收,这就是内存泄露.
2.什么引起内存泄露
上文中还存在一些疑点,GC就是负责回收不再使用的对象的,那么某些对象不再使用了,为什么GC没有把它们回收呢?
这就需要从GC的回收机制谈起:Java使用引用(Reference)的方式访问对象,那么如果ObjectA不再有引用,这时GC就会判定ObjectA是可以回收的,在特定的时刻就会进行回收.比如
1 package test; 2 3 public class ObjectGarbage { 4 5 public static void main(String[] args) { 6 Object objectA = new Object(); 7 Object objectB = new Object(); 8 9 objectB = objectA;//将ObjectA的引用指向ObjectB 10 } 11 12 }
当一个对象失去所有的引用后,GC就可以将其回收,比如此图堆中的Object.反过来说,当不再使用的对象还存在引用时,GC就不能将其回收.这就跟住宾馆一样,住宾馆需要领房卡,只要我没有退房卡,即时这间房间我没有使用,宾馆服务员就不能将这件房间进行回收,让其他房客入住.
见到一篇文章中描述的非常好:
在下面的情境中,对象A引用了对象B. A的生命周期(t1 - t4) 比 B的(t2 - t3)要长得多. 当对象B在应用程序逻辑中不会再被使用以后, 对象 A 仍然持有着 B的引用. (根据虚拟机规范)在这种情况下垃圾收集器不能将 B 从内存中释放. 这种情况很可能会引起内存问题,假若A 还持有着其他对象的引用,那么这些被引用的(无用)对象都不会被回收,并占用着内存空间.
甚至有可能 B 也持有一大堆其他对象的引用。 这些对象由于被 B 所引用,也不会被垃圾收集器所回收. 所有这些无用的对象将消耗大量宝贵的内存空间。
3.内存泄露举例
看到《疯狂Java》中一个很好的例子,在这里描述一下,ArrayList在remove最后一个元素的时候,只需将长度减一即可.比如,先前有四个元素,为删除最后一个元素,将长度变为三,从功能上来说已经完成了删除最后一个元素的目的.但是需要注意,此时ArrayList实例仍然有最后一个元素的引用,但最后一个元素对于ArrayList而言是不会使用的,而GC也不会回收第四个元素.此时就造成了内存泄露.
4.网上内容辨析
在写这篇文章时候,也阅读了不少网上的文章,看到一个例子:
1 Vector v = new Vector(10); 2 for (int i = 1; i < 100; i++) { 3 Object o = new Object(); 4 v.add(o); 5 o = null; 6 }//不推荐做为例子
在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。
文中可能的意思是,o=null,表示o已经没用了,生命周期就应该结束了,而v仍然引用着,GC无法回收,故内存泄露.
我在这里有个疑问,因为v的生命周期还没有结束,此时我想取出第1个元素使用,是可以正确的取出来的,那为什么说Object无用呢?GC在此处没有回收Object是正确的决定.而如果v的生命周期结束了,o就会回收.因此,对于一般的强引用,并不需要特别刻意地去处理它,GC已经能够做得很好了,需要注意的是Java的类定义中隐藏的对象引用。
在此,希望大家不要人云亦云.
5.防止内存泄露
内存泄露,说白了就是,长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收.
需要注意一下几方面:
1)静态变量
静态变量不是实例变量而是类变量,生命周期和应用程序一致,静态变量引用的对象同样不能得到释放.
2)集合类
集合类仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用.这一点其实也不明确,这个集合类如果仅仅是局部变量,根本不会造成内存泄露,在方法栈退出后就没有引用了会被jvm正常回收.而如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减,因此提供这样的删除机制或者定期清除策略非常必要.这里就说到缓存方面的内容.
3)单例模式
不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露.
相关文章:
1.What is a Memory Leak in Java?
4.《疯狂Java》