在工作中,我们有一些运行多个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软件包的源代码会显示扫描检查服务提供程序是否已注册,如果是,则在注册新提供程序之前注销旧提供程序。所以现在的问题是:为什么我有这个巨大的服务提供商链表?为什么它们存储为有向图?更重要的是,我该如何防止这种情况发生?
答案 0 :(得分:3)
对旧问题的回答很晚,但无论如何:
由于ImageIO
插件注册表(IIORegistry
)是“VM global”,因此默认情况下它不适用于servlet上下文。如果您从WEB-INF/lib
或classes
文件夹加载插件,这一点尤为明显,就像OP似乎那样。
Servlet上下文动态加载和卸载类(每个上下文使用一个新的类加载器)。如果重新启动应用程序,默认情况下旧类将永远保留在内存中(因为下次调用scanForPlugins
时,它是扫描/加载类的另一个ClassLoader
,因此它们将成为新的实例。注册表。在测试代码循环中,始终使用相同的ClassLoader
,因此实例被替换,真正的问题永远不会出现)。
要解决此资源泄漏问题,我建议使用ContextListener
来确保明确删除这些“上下文本地”插件。以下是我在几个项目中使用的示例IIOProviderContextListener
,它实现了ImageIO
插件的动态加载和卸载。
答案 1 :(得分:0)
看起来像一个丑陋的内存泄漏给我,我不知道它在哪里没有查看整个代码,但我知道你应该仔细检查。
可能的原因:
_Global Variable List,添加元素,永远不会删除它们。
_Infinite / big循环,将大量元素加载到列表中,永远不会删除它们。
如果您使用Java分析器,例如VisualVM
,它也会有所帮助如果你提供更多信息以便更好地了解它是如何工作的,我可能能够改进我的答案,没有更多的信息我就无能为力了。)
希望它有所帮助!