java - 如何解决以 ImageIO 插件为原因的 OutOfMemoryError?

标签 java out-of-memory javax.imageio

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

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

最佳答案

旧问题的迟到答案,但无论如何:

因为 ImageIO 插件注册表(IIORegistry)是“VM global”,默认情况下它不能很好地与 servlet 上下文一起工作。如果您从 WEB-INF/libclasses 文件夹加载插件,这一点尤其明显,正如 OP 似乎所做的那样。

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

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

关于java - 如何解决以 ImageIO 插件为原因的 OutOfMemoryError?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12357969/

相关文章:

Tomcat内存设置

java - Java 堆转储中的统计信息分割是什么

java - JTable 排序 - 选择一行

java - 如何从文本字符串中删除字符

java - 异常的内存分配

java - 内存不足错误——为什么不进行分页?

java - Base64 到 ImageIO

java - BufferedImage 到 InputStream - 格式不同

java - 为什么 ImageReader 返回不正确的 BufferedImage?

Java奇怪的线程终止