4

在工作中,我们有一些运行多个 webapps 的 tomcat 服务器,其中大约一半必须进行一些图像处理。

在进行图像处理之前,这些 web 应用ImageIO.scanForPlugins()程序会先将适当的图像读取器和写入器放入内存中。虽然之前它只是在需要处理图像的任何时候运行,但我们现在只在 webapps 初始化时运行扫描(因为我们在运行后不添加任何 jar,为什么要多次运行扫描?)

几天后,tomcat 实例由于OutOfMemoryError. 幸运的是,我们HeapDumpOnOutOfMemoryError设置了选项,所以我查看了堆转储。在转储中,我发现 97% 的内存被javax.imageio.spi.PartialOrderIterator. 大部分空间都被它的支持占用了java.util.LinkedList,它有 1800 万个元素。链表由 组成javax.imageio.spi.DigraphNode,其中包含由 加载的图像读取器和写入器ImageIO.scanForPlugins()

“啊哈”,我想,“我们必须在某个地方循环运行扫描,我们只是一遍又一遍地添加相同的元素”。但是,我认为我应该仔细检查这个假设,所以我编写了以下测试类:

import javax.imageio.ImageIO;

public class ImageIOTesting {

public static void main(String[] args) {

    for (int i = 0; i < 100000; i++) {
        ImageIO.scanForPlugins();
        if (i % 1000 == 0) {
            System.out.println(Runtime.getRuntime().totalMemory() / 1024);
        }
    }
}
}

但是,当我在服务器环境中运行这个类时,使用的内存量永远不会改变!

对 javax.imageio 包源的快速挖掘表明,扫描检查是否已注册服务提供者,如果是,则在注册新提供者之前取消注册旧提供者。所以现在的问题是:为什么我有这个巨大的服务提供者链表?为什么将它们存储为有向图?更重要的是,如何防止这种情况发生?

4

2 回答 2

3

一个老问题的迟到的答案,但无论如何:

因为ImageIO插件注册表(IIORegistry)是“VM 全局”的,所以默认情况下它不能很好地与 servlet 上下文配合使用。如果您从WEB-INF/liborclasses文件夹加载插件,这一点尤其明显,就像 OP 似乎做的那样。

Servlet 上下文动态加载和卸载类(每个上下文使用一个新的类加载器)。如果您重新启动应用程序,旧类默认情况下将永远保留在内存中(因为下一次scanForPlugins调用,它是另一个ClassLoader扫描/加载类,因此它们将是注册表中的新实例。在测试代码循环中,相同ClassLoader一直使用,因此实例被替换,真正的问题永远不会出现)。

要解决此资源泄漏问题,我建议使用 aContextListener来确保明确删除这些“上下文本地”插件。IIOProviderContextListener这是我在几个项目中使用的示例,它实现了ImageIO插件的动态加载和卸载。

于 2013-06-28T12:58:57.713 回答
0

对我来说,这看起来像是一个丑陋的内存泄漏,如果不查看整个代码,我无法猜测它在哪里,但我知道你应该非常仔细地检查什么。

可能的原因:

_全局变量列表,添加元素,永远不要删除它们。

_无限/大循环,将大量元素加载到列表中,永远不要删除它们。

如果您使用 Java 分析器(例如VisualVM )也会有所帮助

如果您提供更多信息以便更好地了解它是如何工作的,我可能会改进我的答案,如果没有更多信息,我将无能为力 =)

希望能帮助到你!

于 2012-09-10T19:24:19.423 回答