图片被缓存并占用了我的堆空间

5

这个问题是基于我之前的问题所得到的答案而来。

有人建议我使用Eclipse MAT来调查是什么在消耗我的堆空间。以下是我的观察结果(顶部消费者):

class sun.awt.SunToolkit                                 333.7 MB
com.tennisearth.service.impl.CacheManagerServiceImpl     136 MB
org.apache.jasper.servlet.JspServlet                     91.5 MB

我已经解决了与CacheManageServiceImpl相关的问题,但需要在SunToolkit方面寻求帮助。
以下是创建Image对象的代码(内部使用SunToolkit.imgCache)。
Image img = new ImageIcon(imagePath).getImage();
int imageWidth = img.getWidth(null);
int imageHeight = img.getHeight(null);

请注意,Image对象仅被创建以获取图像的宽度/高度,这在后面的某些逻辑中是必需的。

有没有一种方法可以禁用SunToolkit图像缓存?更好的方法是清除此缓存吗?或者有没有更好的方法可以检索此信息?

顺便提一下,我正在使用以下命令运行jboss(请注意堆大小参数):

java -Dprogram.name=run.sh -server -Xms256m -Xmx1024m -XX:PermSize=64m -XX:MaxPermSize=256m -verbose:gc -Xloggc:/data1/logs/jboss/GC.log -XX:+HeapDumpOnOutOfMemoryError -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dorg.apache.catalina.STRICT_SERVLET_COMPLIANCE=false -Djava.net.preferIPv4Stack=true -Djava.library.path=/usr/local/java/jboss-4.2.2.GA/bin/native -Djava.endorsed.dirs=/usr/local/java/jboss-4.2.2.GA/lib/endorsed -classpath /usr/local/java/jboss-4.2.2.GA/bin/run.jar:/usr/local/java/jdk1.6.0_06/lib/tools.jar org.jboss.Main -c default -b <IP_ADDRESS> -Djboss.messaging.ServerPeerID=1

Sumit


1
Java相关的图像和图像缓存存在很多问题。恶名昭彰的flush()解决方法是API缺陷的一个例子。仅仅将图像置空并不总是足够的:有时您需要自己调用flush(),但这根本没有意义。如果它在服务器上,我会通过运行外部ImageMagick的identify命令来获取宽度/高度信息。运行外部进程有点麻烦,但是在服务器上加载图像只是为了获取其宽度/高度也是相当重量级的。 - SyntaxT3rr0r
另一个选项是...我们正在批处理所有图片并直接将宽度/高度信息放入图片文件名中:mypic.jpg变为mypic640x480.jpg。然后我们只需从文件名中提取(w,h)即可... - SyntaxT3rr0r
如果你想在这个问题上进行谷歌搜索,这里有一个有趣的引用:“我们已经在除了OS X之外的其他平台上看到了刷新问题,所以我认为“正确”的修复需要由Sun来实现,在ToolkitImage上实现finalize以强制在GC回收图像之前进行刷新。有人向Sun报告了这个问题吗?” - SyntaxT3rr0r
我在数百个不同的Windows/OS X上发布了一个使用大量图像的Java应用程序,我可以告诉你:肯定还有各种各样的错误,我们看到了OutOfMemory错误,如果这些API被正确设计/实现,这些错误本不应该发生。 - SyntaxT3rr0r
@SyntaxT3rr0r:感谢您的回复。不幸的是,使用外部命令不是一个选项,我也想利用内部缓存机制来加速处理过程。根据Jorn的评论,缓存到期由JVM本身处理,所以我不必担心它。 - Sumit
2个回答

3
图像缓存似乎是由一个名为SoftCache的类实现的,它的文档说明如下:
一种内存敏感的Map接口实现。 SoftCache对象使用java.lang.ref.SoftReference来实现内存敏感的哈希映射。如果垃圾收集器在某个时间点确定SoftCache条目中的值对象不再是强可达的,则可能会删除该条目以释放值对象占用的内存。所有SoftCache对象都保证在虚拟机抛出OutOfMemoryError之前完全清除。
因此,我不必担心此缓存占用的内存,因为当内存需要用于其他地方时,它会自动清除。

编辑:阅读了SyntaxT3rr0r的评论后,我认为仍然值得在图像上调用flush。如果这是一个较大的方法的一部分,您还可以将图像设置为null或重构以更快地超出范围。

另一个可能性是尝试使用ImageIO Api检索宽度和高度。这应该是通过获取图像类型的ImageReader来实现的。


1
非常感谢您的澄清。很高兴我不再需要担心这个问题了 :) - Sumit
刚看到你的编辑,我仍然想要使用图像缓存来加快创建图像对象的过程。我相信刷新图像将无法使用此缓存。正确吗? - Sumit
1
@Sumit,我不确定这个调用会清除哪些缓存。如果您反复查询相同图像路径的宽度和高度,实现一个简单的缓存可能也是有意义的,比如一个 Map<String, WidthAndHeightBean>。这将避免在 imgCache 被 gc 清除后重新创建图像的需要。 - Jörn Horstmann
我遇到了一个确切的问题,即图像缓存将非常大的图像在内存中保留时间稍长。当我尝试加载另一个大图像时,由于GC似乎没有及时运行,它会出现内存不足的情况。也就是说,我们加载40或50 MB的图像并将它们转换为较小的图像。在每次加载图像之间通过jconsole强制执行GC可以解决这个问题,但在正常操作期间,有时会出现内存不足的情况。 - Neil Wightman

1

你的Image对象是否可能在长时间内保持在作用域中?例如,如果它包含在长时间运行的代码块的外部作用域中,它可能无法被正确地垃圾回收。

有时(在极少数情况下),明确将Image对象引用设置为null是有益的。这在我上面提到的情况下是正确的。请参阅以下问题以获取更多信息:在Java中将对象分配给null会影响垃圾回收吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接