Figure 1. The JRockit Management Console
管理控制台甚至可以配置成在堆使用量出现问题(或者其他的事件发生)时向你发送邮件。这个显然使得监控内存泄漏更加容易。 内存泄漏探测工具 有很多专门的内存泄漏探测工具。其中The JRockit Memory Leak Detector可以供来观察内存泄漏也可以针对性地找到泄漏的原因。这个强大的工具被紧密地集成在JRockit JVM中,可以提供最低可能的内存事务也可以轻松的访问虚拟机的堆。 专门工具的优势 一旦你知道程序中存在内存泄漏,你需要更专业的工具来查明为什么这里会有泄漏。而JVM是不可能告诉你的。现在有很多工具可以利用了。这些工具本质上主要通过两种方法来得到JVM的存储系统信息的:JVMTI和字节码仪器。Java虚拟机工具接口(JVMTI)和他的原有形式JVMPI(压型接口,profiling Interface)都是标准接口,作为外部工具同JVM进行通信,搜集JVM的信息。字节码仪器则是引用通过探针获得工具所需的字节信息的预处理技术。 通过这些技术来侦测内存泄漏存在两个缺点,而这使得他们在产品级环境中的运用不够理想。首先,根据两者对内存的使用量和内存事务性能的降级是不可以忽略的。从JVM获得的堆的使用量信息需要在工具中导出,收集和处理。这意味着要分配内存。按照JVM的性能导出信息是需要开销的,垃圾回收器在搜集信息的时候是运行的非常缓慢的。另一个缺点就是,这些工具所需要的信息是关系到JVM的。让工具在JVM开始运行的时候和它关联,而在分析的时候,分离工具而保持JVM运行,这显然是不可能的。 既然JRockit Memory Leak Detector是被集成到JVM中的,那么以上两种缺点就不再存在。首先,大部分的处理和分析都是在JVM中完成的,所以就不再需要传送或重建任何数据。处理也可以建立在垃圾回收器的基础上,即提高速度。再有,内存泄漏侦测器可以同一个运行的JVM关联和分离,只要JVM在开始的时候伴随着 –Xmanagement选项(通过远程JMX接口允许监听和管理JVM)。当工具分离以后,工具不会遗留任何东西在JVM中;JVM就可以全速运行代码就好像工具关联之前一样。 趋势分析 让我们更深一步来观察这个工具,了解他如何捕捉到内存泄漏。在你了解到代码中存在内存泄漏,第一步就是尝试计算出什么数据在泄漏——哪个对象类导致泄露。The JRockit Memory Leak Detector通过在垃圾回收的时候,计算每个类所包含的现有的对象来达到目的。如果某一个类的对象成员数目随着时间增长(增长率),那么这里很可能存在泄漏。
Figure 2. The trend analysis view of the Memory Leak Detector
因为一个泄漏很可能只是像水滴一样小,所以趋势分析必须运行足够长的一段时间。在每个短暂的时间段里,局部类的增加会使得泄漏发生推迟。但是,内存事务是非常小的(最大的内存事务是由在每个垃圾回收时从JRockit向内存泄漏探测器发送的一个数据包组成的)。内存事务不应该成为任何系统的问题——甚至一个在产品阶段全速运行的程序。 一开始,数字会有很大的跳转,随时间的推进,这些数字会变得稳定,而后显示哪些类会不断的增大。 寻找根本原因 知道那些对象的类会导致泄露,有时候足够制止泄露问题。这个类也许只是被用在非常有限的部分,通过快速的视察就可以找到问题所在。不幸的是,这些信息是不够的。比方说,经常导致内存泄漏的对象类java.lang.String,然而String类被应用于整个程序,这就变得有些无助。 我们想知道的是其他的对象是否会导致内存泄漏,好比上面提到的String类,为什么这些导致泄漏的对象还是在周围存在?哪些引用是指向这些对象的?如果列出所有引用String的对象,工作就会变得太大而没有实际意义。为了限制数据的数量,我们可以通过类把他们编成一个组,这样我们就可以看到,那些其他类的对象会依然泄漏对象(String类)。比如,将一个String类放入Hashtable,那里我们可以看到关联到String类的Hashtable入口。从Hashtable入口向后运行,我们终于找到那些关联到String类的Hashtable对象(参看图三如下)。
Figure 3. Sample view of the type graph as seen in the tool
向后工作 自从开始我们就一直着眼于对象类,而不是单独的对象,我们不知道那个Hashtable存在泄漏。如果我们可以找出所有的Hashtable在系统中有多大,我们可以假设最大的那个Hashtable存在泄漏(因为它可以聚集足够的泄漏而变得很大)。因此,所有Hashtable,同时有和所有他们所涉及的数据,可以帮助我们查明导致泄露的精确的Hashtable。
Figure 4. Screenshot of the list of Hashtable objects and the size of the data they are holding live
计算一个对象所涉及的数据的开销是非常大的(这要求引用图表伴随着那个对象作为根运行)而且如果对每一个对象都这样处理,就需要很多时间。知道一些关于Hashtable内部的实现机制可以带来捷径。在内部,一个Hashtable有一个Hashtable的数组入口。数组的增长伴随着Hashtable中对象的增长。因此,要找到最大的Hashtable,我们可以把搜索限制在寻找包含Hashtable引用入口的最大的数组。这样就更快捷了。

向下深入 当我们发现了存在泄漏的Hashtable的实例,就可以顺藤摸瓜找到其他的引用这些Hashtable的实例,然后用上面的方法来找到是那个Hashtable存在问题。
Figure 6. This is what an instance graph can look like in the tool.
举个例子,一个Hashtable可以有一个来自MyServer的对象的引用,而MyServer包含一个activeSessions数据成员。这些信息就足够深入代码找出问题所在。
Figure 7. Inspecting an object and its references to other objects
找出分配点 当发现了内存泄漏问题,找到那些泄漏的对象在何处是非常有用的。也许没有足够的信息知道他们同其他相关对象之间的联系,但是关于他们在那里被创建的信息还是很有帮助的。当然,你不会愿意创建一个工具来打印出所有分配的堆栈路径。你也不会愿意在模拟环境中运行程序只是为了捕捉到一个内存泄漏。 有了JRockit Memory Leak Detector,程序代码可以动态的在内存分配出创建堆栈路径。这些堆栈路径可以在工具中累积,分析。如果你不启用这个工具,这个特征就不会有任何消耗,这就意味着时刻准备着开始。当需要分配路径时,JRockit的编译器可以让代码不工作,而监视内存分配,但只对需要的特定类有效。更好的是,当做完数据分析后,生成的机械代码会完全被移除,不会引起任何执行上的效率衰退。
Figure 8. The allocation stack traces for String during execution of a sample program
总结 内存泄漏查找起来非常困难,文章中的一些避免泄漏的好的实践,包括了要时刻记住把什么放进了数据结构中,更接近的监视内存中意外的增长。 我们同时也看到了JRockit Memory Leak Detector是如何捕捉产品级系统中的内存泄漏的。该工具通过三步的方法发现泄漏。一,通过趋势分析发现那些对象类存在泄漏;二,找出同泄漏对象相关的其他类;三,向下发掘,观察独立的对象之间是如何相互联系的。同时,该工具也可以动态的,找出所有内存分配的堆栈路径。利用这三个特性,将该工具紧紧地集成在JVM中,那么就可以安全的,有效的捕捉和修复内存泄漏了。 资源 JRockit Tools Download BEA JRockit 5.0 Documentation New Features and Tools in JRockit 5.0 BEA JRockit DevCenter Staffan Larsen是JRockit项目的工程师之一,这个项目是在1998年底他与别人联合创建的。