在工作中,我们有一些 Tomcat 服务器运行多个 Web 应用程序,其中大约一半必须执行一些图像处理。
在进行图像处理之前,这些网络应用程序会执行以下操作ImageIO.scanForPlugins()
将适当的图像读取器和写入器放入内存中。虽然之前只是在需要处理图像的任何时候运行,但现在我们仅在 Web 应用程序初始化时运行扫描(因为我们在运行后不添加任何 jar,为什么要多次运行扫描?)
几天后,tomcat 实例因以下原因崩溃了:OutOfMemoryError
。幸运的是我们有HeapDumpOnOutOfMemoryError
选项集,所以我查看了堆转储。在转储中我发现 97% 的内存被 a 的实例占用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 全局”,默认情况下它不能很好地与 servlet 上下文配合使用。如果您从以下位置加载插件,这一点尤其明显WEB-INF/lib
or classes
文件夹,正如OP似乎所做的那样。
Servlet 上下文动态加载和卸载类(每个上下文使用一个新的类加载器)。如果您重新启动应用程序,默认情况下旧类将永远保留在内存中(因为下一次scanForPlugins
被称为,这是另一个ClassLoader
扫描/加载类,因此它们将成为注册表中的新实例。在测试代码循环中,同样ClassLoader
一直使用,因此实例被替换,真正的问题永远不会显现)。
要解决此资源泄漏问题,我建议使用ContextListener
确保这些“上下文本地”插件被明确删除。这是一个例子IIOProviderContextListener我在几个项目中使用过,它实现了动态加载和卸载ImageIO
插件。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)