寻找原因
要想将测试人员提交的问题隔离出来,第一步就是提供一些简单的、重复的测试用例。以上面那个例子为例,我发现简单地添加一个表单,删除这个表单,然后强制垃圾收集器的结果是一些关联到已经删除掉的表单的实例仍然存活着。这种问题通过 JProbe实例摘要视图来看是显而易见的,视图中统计了堆内存中每个类的实例的个数。
要定位垃圾收集器工作时具体实例的引用,我使用了 JProbe 的引用画面,如下图所示,来断定哪些类仍然在引用已被删除掉的 FormFrame 类。这是调试这种问题的巧妙地方法之一,我通过它发现了很多不同的对象仍然在引用那些无用的对象。而通过试错来查明究竟是哪个引用者真正造成这个问题的过程却是相当耗时的。
在这个案例中,根类(左上角红色的那个)是出现问题的起源。右侧用蓝色突出的那个类就是追踪到的 FormFrame 类。
对于这个具体的例子,找到的罪魁祸首是一个包含一个静态的哈希表的字体管理类。通过引用列表追踪后,我发现根节点是一个静态的哈希表,这个哈希表保存了每个表单使用的字体。各种表单可以被独立地放大或缩小,所以哈希表包含了一个具有每个指定的表单的所有字体的向量。当表单的缩放视图改变时,带有字体的向量被获取并选择合适的缩放因素来适应字体大小。
这个字体管理器的问题是,在创建表单时,当代码将字体向量放进哈希表时,却没有定义表单删除时对向量的移除。因此,这个在整个应用程序的生命周期都存在的静态的哈希表,却从来没有移除指向每个表单的键值。所以,所有的表单和其相关联的类被遗留在了内存中。