如何解决OutOfMemoryError与ImageIO插件的原因?

时间:2012-09-10 19:10:53

标签: java out-of-memory javax.imageio

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

在进行图像处理之前,这些webapp会执行ImageIO.scanForPlugins()以将适当的图像读取器和编写器放入内存中。虽然在此之前只需要处理图像,但我们现在只在初始化webapp时运行扫描(因为我们在运行后没有添加任何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软件包的源代码会显示扫描检查服务提供程序是否已注册,如果是,则在注册新提供程序之前注销旧提供程序。所以现在的问题是:为什么我有这个巨大的服务提供商链表?为什么它们存储为有向图?更重要的是,我该如何防止这种情况发生?

2 个答案:

答案 0 :(得分:3)

对旧问题的回答很晚,但无论如何:

由于ImageIO插件注册表(IIORegistry)是“VM global”,因此默认情况下它不适用于servlet上下文。如果您从WEB-INF/libclasses文件夹加载插件,这一点尤为明显,就像OP似乎那样。

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

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

答案 1 :(得分:0)

看起来像一个丑陋的内存泄漏给我,我不知道它在哪里没有查看整个代码,但我知道你应该仔细检查。

可能的原因:

_Global Variable List,添加元素,永远不会删除它们。

_Infinite / big循环,将大量元素加载到列表中,永远不会删除它们。

如果您使用Java分析器,例如VisualVM

,它也会有所帮助

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

希望它有所帮助!