Java中一个最重要的优势之一就是它的内存管理。你简单地创建一些对象后,Java垃圾收集器就会帮你为这些对象分配和回收它们的内存。
然而,真实情况往往没有这么简单,因为内存泄漏的问题在Java应用程序中经常出现。
本教程阐释了什么是内存泄漏,为什么会出现内存泄漏和如何防止内存泄漏的发生。
什么是内存泄漏?
内存泄漏的定义:当对象不再被应用程序使用后,因为还有引用指向它们而导致无法被垃圾收集器回收。
为了弄清楚这个定义,我们需要明白对象在内存中的状态。下面这个图显示了什么是不再被使用和不再被引用。
这个图中存在被引用的对象和不被引用的对象。不被引用的对象将会被垃圾收集器回收,被引用的对象则不会。没被引用的对象一定是毫无用处的
因为已经没有引用指向它了。但是,不再被使用的对象却不都是没有引用指向的,也就是说其中一部分还存在引用指向不再使用的对象。这就是内存泄漏的来源了。
为什么会发生内存泄漏呢?
让我们来看看下面这个例子,了解一下内存泄漏发生的原因。下面的例子中,对象A指向对象B,但是A的生命周期(t1-t4)远远大于对象B的生命周期(t2-t3)。
当对象B不再使用时,对象A仍然保有一个引用指向B。从这个角度看,垃圾收集器是无法将释放分配给对象B的内存空间的。这将可能导致内存耗尽的问题,
因为如果对象A对更多的对象做着同样的事情,那么将会有很多对象不能被回收而导致内存空间的消耗。
也存在另一种可能,对象B保有一大堆指向别的对象的引用的时候,这些被对象B引用的对象也将不能被回收。所有这些不再使用的对象将占用宝贵的内存空间。
如何防止内存泄漏呢?
下面给出几点快速上手的防止内存泄漏的贴士:
1.注意集合类,比如HashMap,ArrayList等等,因为它们使用的地方都是经常能发现内存泄漏的地方。当它们被声明为静态的时候,它们的生命周期
就将和应用程序一样长。
2.注意时间监听器(event listeners)和回调函数(callbacks),如果注册了一个监听器但没有在类不被使用后取消注册将有可能发生内存泄漏。
3."如果一个类管理自己的内存,开发者应该警惕内存泄漏问题。"[1]很多时候一个对象的成员变量指向其他对象的需要被赋值为null。
一个小练习:为什么substring()方法在JDK6中会导致内存泄漏呢?
为了回答这个问题,你也许想读读substring() in JDK6 AND 7。
参考资料:
[1] Bloch, Joshua. Effective java. Addison-Wesley Professional, 2008.
[2] IBM Developer Work. http://www.ibm.com/developerworks/library/j-leaks/
译者注:管理自己内存的类,类似于集合类、事件监听器,看了文章评论后得出的个人看法。
这边文章阐述了一些关于内存泄漏的基本概念,substring() in JDK6 AND 7这个链接中讲述了JDK7和JDK6中substring()方法的不同实现,将会给我们关于内存泄漏更感性的认识,值得一读。