【发布时间】:2014-07-10 02:58:07
【问题描述】:
我们有一个提供图像的应用程序,为了加快响应时间,我们将BufferedImage 直接缓存在内存中。
class Provider {
@Override
public IData render(String... layers,String coordinate) {
int rwidth = 256 , rheight = 256 ;
ArrayList<BufferedImage> result = new ArrayList<BufferedImage>();
for (String layer : layers) {
String lkey = layer + "-" + coordinate;
BufferedImage imageData = cacher.get(lkey);
if (imageData == null) {
try {
imageData = generateImage(layer, coordinate,rwidth, rheight, bbox);
cacher.put(lkey, imageData);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
if (imageData != null) {
result.add(imageData);
}
}
return new Data(rheight, rheight, width, result);
}
private BufferedImage generateImage(String layer, String coordinate,int rwidth, int rheight) throws IOException {
BufferedImage image = new BufferedImage(rwidth, rheight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.RED);
g.drawString(layer+"-"+coordinate, new Random().nextInt(rwidth), new Random().nextInt(rheight));
g.dispose();
return image;
}
}
class Data implements IData {
public Data(int imageWidth, int imageHeight, int originalWidth, ArrayList<BufferedImage> images) {
this.imageResult = new BufferedImage(this.imageWidth, this.imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = imageResult.createGraphics();
for (BufferedImage imgData : images) {
g.drawImage(imgData, 0, 0, null);
imgData = null;
}
imageResult.flush();
g.dispose();
images.clear();
}
@Override
public void save(OutputStream out, String format) throws IOException {
ImageIO.write(this.imageResult, format, out);
out.flush();
this.imageResult = null;
}
}
用法:
class ImageServlet extends HttpServlet {
void doGet(req,res){
IData data= provider.render(req.getParameter("layers").split(","));
OutputStream out=res.getOutputStream();
data.save(out,"png")
out.flush();
}
}
注意:provider 字段是单个实例。
但似乎可能存在内存泄漏,因为当应用程序继续运行大约 2 分钟时,我会收到 Out Of Memory 异常。
然后我使用visualvm来检查内存使用情况:
即使我手动Perform GC,内存也无法释放。
虽然只有 300+ BufferedImage 被缓存,20M+ 内存被使用,1.3G+ 内存被保留。事实上,通过“萤火虫”我可以确保生成的图像小于1Kb。所以我认为内存使用不健康。
一旦我不使用缓存(注释以下行):
//cacher.put(lkey, imageData);
内存使用看起来不错:
看来缓存的BufferedImage 会导致内存泄漏。
然后我尝试将BufferedImage 转换为byte[] 并缓存byte[] 而不是对象本身。而且内存使用还是正常的。但是我发现Serialization 和Deserialization 的BufferedImage 会花费太多时间。
所以我想知道你们是否有任何图像缓存的经验?
更新:
由于很多人说没有内存泄漏但是我的缓存器使用了太多内存,我不确定但我尝试缓存byte[]而不是直接缓存BufferedImage,内存使用看起来不错.而且我无法想象322张图片会占用1.5G+内存,正如@BrettOkken所说,总大小应该是(256 * 256 * 4byte) * 322 / 1024 / 1024 = 80M,远远小于1Gb。
而刚才,我改为缓存byte,再次监控内存,代码变化如下:
BufferedImage ig = generateImage(layer,coordinate rwidth, rheight);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(ig, "png", bos);
imageData = bos.toByteArray();
tileCacher.put(lkey, imageData);
以及内存使用情况:
相同的代码,相同的操作。
【问题讨论】:
-
图片是彩色还是灰度?如果是灰度,每像素 8 位还是 16 位?如果颜色,什么颜色型号?图片的分辨率是多少?如果图像小于 1 KB,则表示它是 8 位灰度且小于 32 x 32 像素。
-
所有生成的图片都使用
BufferedImage.TYPE_INT_ARGB的类型。大小不到1kb,因为我只是画了一些字符串。 -
BufferedImages 未压缩。他们有数据的支持数组(在你的情况下可能是一个 byte[]),它将根据颜色模型(在你的情况下可能是 4 个)为每个像素分配值。所以消耗的内存量大约是宽*高*4。
-
那么300+
BufferedImage使用的内存好像是正常的,但是保留的内存大小一直在增加。 -
我认为我们已经确定这不是内存泄漏。是否可以缓存 servlet 的输出(完整生成的图像),而不是中间层?或者您的数据是完全动态的,因此每个响应都是独一无二的?如果您可以缓存 servlet 响应,您可能会同时节省 CPU 和内存。也许添加一个 HTTP 缓存(反向代理或类似的,如 nginx 或 varnish),也可以卸载 JVM 堆。
标签: java caching memory-leaks bufferedimage