诊断Java“内存泄漏”的一般性方法

 

  www.InnovateDigital.com

  OOM(Out-of-Memory)是导致应用服务器/JVM不稳定的常见问题。OOM的一个重要原因是系统存在Java“内存泄漏”问题。“内存泄漏”问题是对象的生命周期问题的一种。可以理解为, 某些对象的生命周期过长,未及时清理,出现了“内存泄漏”。

  一旦发现了“内存泄漏”的征兆,就需要专业的工具来查明为什么会发生“泄漏”。JVM本身不会提供答案。这些专业工具从JVM获得内存系统信息,方法基本上有两种:JVMTI和字节码注入技术(byte code instrumentation)。Java虚拟机工具接口(Java Virtual Machine Tools Interface,JVMTI)及其前身Java虚拟机监视程序接口(Java Virtual Machine Profiling Interface,JVMPI)是外部工具与JVM通信并从JVM收集信息的标准化接口。字节码注入技术是指使用探测器处理字节码以获得工具所需信息的技术。

  Quest JProbe Suite,主要用于帮助对软件系统进行代码优化和故障诊断,可用于Java“内存泄漏”的分析以及生命周期对象的测量。

  首先,这类工具会进行趋势分析,找出是哪个类的对象在“泄漏”。系统长时间运行中可以得到多个内存快照。对这多个内存快照进行综合分析,如果每一次快照的内存使用都比上一次有增长,可以认定系统存在内存泄漏,找出在四个快照中实例个数都保持增长的类,这些类可以初步被认定为存在“泄漏”。通过数据收集和初步分析,可以得出初步结论:系统是否存在内存泄漏和哪些对象存在“泄漏”(或“被泄漏”)。

  接下来,看看有哪些类与“泄漏”的类的对象相关联。Java中的内存泄漏就是保持对无用对象的引用,简单地说就是因为编码的错误导致了一条本来不应该存在的引用的存在(从而导致了被引用的对象无法释放),因此“内存泄漏”分析的任务就是找出这条多余的引用链,并找到其形成的原因。查看该对象在那里分配的,这是是很有价值的。只知道它们如何与其他对象相关联(即哪些对象引用了它们)是不够的,关于它们是在何处被创建的信息也很有用。

  最后,进一步研究单个可疑的“泄漏”对象,看看它们是如何互相关联的。借助于JProbe工具,可以跟踪自己应用代码中对象的创建堆栈,也可以对系统中所有对象分配进行堆栈跟踪。这些堆栈跟踪可以在工具中进行累积和分析。每个被“泄漏”的实例对象,必然存在一条从某个引用对象出发到达该对象的引用链。处于堆栈空间的引用对象在被从栈中弹出后就失去其引用,变为非引用对象。

  经过多年的实践总结,常见的Java“内存泄漏”大致有以下几种类型,可以帮助技术人员快速诊断问题:

  静态集合类

  HashMap、Vector、ArrayList

  等静态集合类的使用最容易引起“内存泄漏”,因为这些静态变量的生命周期与应用程序一致,这些对象生命周期过长。

  监听器

  在java编程中,我们常常需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了“内存泄漏”的机会。

  物理连接

  一些物理连接,比如数据库连接和网络连接,除非显式关闭了连接,否则是不会自动被GC 回收的。Java数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset和Statement 对象可以不进行显式回收(取决于JDBC的具体实现),但Connection 一定要显式回收,因为Connection在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。

  内部类和外部模块等的引用

  内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。对于程序员而言,自己的程序很清楚,如果发现内存泄漏,自己对这些对象的引用可以很快定位并解决,但是现在的应用软件并非一个人实现,模块化的思想在现代软件中非常明显,所以程序员要小心外部模块不经意的引用。

  例如程序员A 负责A 模块,调用了B 模块的一个方法如:

  public void registerMsg(Object b);

  这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。

  Java“内存泄漏”,关键要在发现有“内存泄漏”的时候能用好的测试工具迅速定位问题的所在。Quest JProbe是目前市场上依然不断发展,具有较好口碑的专业检查Java “内存泄漏”的工具,通过监测Java程序运行,包括所有对象的分配、释放等操作,将内存管理的所有信息进行统计、分析、可视化。开发人员可根据这些信息诊断程序代码是否有“内存泄漏”问题。

  JProbe Suite是诊断Java性能问题的有力工具,可以通过附件文档的示例深入了解诊断“内存泄漏”等Java对象生命周期问题。

  www.InnovateDigital.com