如何解决以 ImageIO 插件为原因的 OutOfMemoryError? [英] How to resolve OutOfMemoryError with ImageIO plugins as the cause?

查看:77
本文介绍了如何解决以 ImageIO 插件为原因的 OutOfMemoryError?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

At work we have some tomcat servers running several webapps, about half of which have to do some image processing.

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

Before doing their image processing, these webapps do a ImageIO.scanForPlugins() to get the appropriate image readers and writers into memory. While before this was just run anytime an image needed to be processed, we now run the scan only when the webapps are initialized (since we don't add any jars after running, why run the scan more than once?)

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

Some days later, the tomcat instance crashed due to an OutOfMemoryError. Luckily we had the HeapDumpOnOutOfMemoryError option set, so I looked at the heap dump. In the dump I found that 97% of memory was taken by an instance of a javax.imageio.spi.PartialOrderIterator. Most of that space was taken up by it's backing java.util.LinkedList, which had 18 million elements. The linked list is made up of javax.imageio.spi.DigraphNode, which contains the image readers and writers loaded by ImageIO.scanForPlugins().

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

"Aha", I thought, "we must be running the scan in a loop somewhere and we're just adding the same elements over and over again". But, I figured I should double check this assumption, so I wrote the following test class:

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);
        }
    }
}
}

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

However, when I run this class on the server environment, the amount of memory in use never changes!

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

A quick dig through the source of the javax.imageio packages shows that the scan checks to see if a service provider is already registered, and if so it deregisters the old provider before registering the new one. So now the question is: Why do I have this giant linked list of service providers? Why are they stored as a directed graph? And more importantly, how do I prevent this from happening?

推荐答案

对一个老问题的回答晚了,但无论如何:

Late answer to an old question, but anyway:

因为 ImageIO 插件注册表(IIORegistry)是VM global",所以默认情况下它不适用于 servlet 上下文.如果您从 WEB-INF/libclasses 文件夹加载插件,这一点尤其明显,就像 OP 所做的那样.

Because the ImageIO plugin registry (the IIORegistry) is "VM global", it doesn't by default work well with servlet contexts. This is especially evident if you load plugins from the WEB-INF/lib or classes folder, as the OP seems to do.

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

Servlet contexts dynamically loads and unloads classes (using a new class loader per context). If you restart your application, old classes will by default remain in memory forever (because the next time scanForPlugins is called, it's another ClassLoader that scans/loads classes, and thus they will be new instances in the registry. In the test code loop, the same ClassLoader is used all the time, thus instances are replaced, and the real problem never manifests).

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

To work around this resource leak, I recommend using a ContextListener to make sure these "context local" plugins are explicitly removed. Here's an example IIOProviderContextListener I've used in a couple of projects, that implements dynamic loading and unloading of ImageIO plugins.

这篇关于如何解决以 ImageIO 插件为原因的 OutOfMemoryError?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆