垃圾收集器的作用
虽然垃圾收集器处理了大多数内存管理问题,从而使编程人员的生活变得更轻松了,但是编程人员还是可能犯错而导致出现内存问题。简单地说,GC循环地跟踪所有来自“根”对象(堆栈对象、静态对象、JNI句柄指向的对象,诸如此类)的引用,并将所有它所能到达的对象标记为活动的。程序只可以操纵这些对象;其他的对象都被删除了。因为GC使程序不可能到达已被删除的对象,这么做就是安全的。
虽然内存管理可以说是自动化的,但是这并不能使编程人员免受思考内存管理问题之苦。例如,分配(以及释放)内存总会有开销,虽然这种开销对编程人员来说是不可见的。创建了太多对象的程序将会比完成��的功能而创建的对象却比较少的程序更慢一些(在其他条件相同的情况下)。
而且,与本文更为密切相关的是,如果忘记“释放”先前分配的内存,就可能造成内存泄漏。如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存,这是因为自动化的垃圾收集器无法证明这些对象将不再使用。正如我们先前所说的,如果存在一个对对象的引用,对象就被定义为活动的,因此不能删除。为了确保能回收对象占用的内存,编程人员必须确保该对象不能到达。这通常是通过将对象字段设置为null或者从集合(collection)中移除对象而完成的。但是,注意,当局部变量不再使用时,没有必要将其显式地设置为null。对这些变量的引用将随着方法的退出而自动清除。
概括地说,这就是内存托管语言中的内存泄漏产生的主要原因:保留下来却永远不再使用的对象引用。
典型泄漏
既然我们知道了在Java中确实有可能发生内存泄漏,就让我们来看一些典型的内存泄漏及其原因。
全局集合
在��的应用程序中有某种全局的数据储存库是很常见的,例如一个JNDI树或一个会话表。在这些情况下,必须注意管理储存库的大小。必须有某种机制从储存库中移除不再需要的数据。
这可能有多种方法,但是最常见的一种是周期性运行的某种清除任务。该任务将验证储存库中的数据,并移除任何不再需要的数据。
另一种管理储存库的方法是使用反向链接(referrer)计数。然后集合负责统计集合中每个入口的反向链接的数目。这要求反向链接告诉集合何时会退出入口。当反向链接数目为零时,该元素就可以从集合中移除了。
缓存
缓存是一种数据结构,用于快速查找已经执行的操作的结果。因此,如果一个操作执行起来很慢,对于常用的输入数据,就可以将操作的结果缓存,并将���次调用该操作时使用缓存的数据。
缓存通常都是以动态方式实现的,其中新的结果是在执行时添加到缓存中的。典型的算法是:
* 检查结果是否在缓存中,如果在,就返回结果。
* 如果结果不在缓存中,就进行计算。
* 将计算出来的结果添加到缓存中,以便以后对该操作的调用可以使用。
该算法的问题(或者说是潜在的内存泄漏)出在最后一步。如果调用该操作时有相当多的不同输入,就将有相当多的结果存储在缓存中。很明显这不是正确的方法。
为了预防这种具有潜在破坏性的设计,程序必须确保对于缓存所使用的内存容量有一个上限。因此,更好的算法是:
* 检查结果是否在缓存中,如果在,就返回结果。
* 如果结果不在缓存中,就进行计算。
* 如果缓存所占的空间过大,就移除缓存最久的结果。
* 将计算出来的结果添加到缓存中,以便以��对该操作的调用可以使用。
通过始终移除缓存最久的结果,我们实际上进行了这样的假设:在将来,比起缓存最久的数据,最近输入的数据更有可能用到。这通常是一个不错的假设。
新算法将确保缓存的容量处于预定义的内存范围之内。确切的范围可能很难计算,因为缓存中的对象在不断变化,而且它们的引用包罗万象。为缓存设置正确的大小是一项非常复杂的任务,需要将所使用的内存容量与检索数据的速度加以平衡。
解决这个问题的另一种方法是使用java.lang.ref.SoftReference类跟踪缓存中的对象。这种方法保证这些引用能够被移除,如果虚拟机的内存用尽而需要更多堆的话。
ClassLoader
Java ClassLoader结构的使用为内存泄漏提供了许多可乘之机。正是该结构本身的复杂性使ClassLoader在内存泄漏方面存在如此多的问题。 ClassLoader的特别之处在于它不仅涉及“常规”的对象引用,还涉及元对象引用,比如:字段、方法和类。这意味着只要有对字段、方法、类或 ClassLoader的对象的引用,ClassLoader就会驻留在JVM中。因为ClassLoader本身可以关联许多类及其静态字段,所以就有许多内存被泄漏了。
确定泄漏的位置
通常发生内存泄漏的第一个迹象是:应用���序中出现了OutOfMemoryError。这通常发生在您最不愿意它发生的生产环境中,此时几乎不能进行调试。有可能是因为
虽然OutOfMemoryError通常都是内存泄漏的信号,但是也有可能应用程序确实正在使用这么多的内存;对于后者,或者必须增加JVM 可用的堆的数量,或者对应用程序进行某种更改,使它使用较少的内存。但是,在许多情况下,OutOfMemoryError都是内存泄漏的信号。一种查明方法是不间断地监控GC的活动,确定内存使用量是否随着时间增加。如果确实如此,就可能发生��内存泄漏。
详细输出
有许多监控垃圾收集器活动的方法。而其中使用最广泛的可能是使用-Xverbose:gc选项启动JVM,并观察输出。
[memory ] 10.109-10.235: GC 65536K->16788K (65536K), 126.000 ms
箭头后面的值(本例中是16788K)是垃圾收集所使用的堆的容量。
控制台
查看连续不断的GC的详细统计信息的输出将是非常乏味的。幸好有这方面的工具。JRockit Management Console可以显示堆使用量的图示。借助于该图,可以很容易地看出堆使用量是否随时间增加。
甚至���以配置该管理控制台,以便如果发生堆使用量过大的情况(或基于其他的事件),控制台能够向您发送电子邮件。这明显使内存泄漏的查看变得更容易了。
内存泄漏检测工具
还有其他的专门进行内存泄漏检测的工具。JRockit Memory Leak Detector可以用来查看内存泄漏,并可以更深入地查出泄漏的根源。这个强大的工具是紧密
该贴被ganxinge编辑于2013-2-27 15:16:56
该贴由koei转至本版2014-5-2 16:07:38