在工作中,我们有一些运行多个 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 包源的快速挖掘表明,扫描检查是否已注册服务提供者,如果是,则在注册新提供者之前取消注册旧提供者。所以现在的问题是:为什么我有这个巨大的服务提供者链表?为什么将它们存储为有向图?更重要的是,如何防止这种情况发生?