缓存BufferedImage时可能发生内存泄漏

hgu*_*ser 9 java caching memory-leaks bufferedimage

我们有一个提供图像的应用程序,为了加快响应时间,我们将其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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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();

    }
}
Run Code Online (Sandbox Code Playgroud)

注意:该provider字段是单个实例.

但是,似乎存在可能的内存泄漏,因为Out Of Memory当应用程序继续运行大约2分钟时,我将获得异常.

然后我visualvm用来检查内存使用情况:

在此输入图像描述

即使我Perform GC手动,内存也无法释放.

虽然只有300多个BufferedImage缓存,并且20M+使用了1.3G+内存,但内存仍然保留.事实上,通过"firebug",我可以确保生成的图像小于1Kb.所以我认为内存使用不健康.

一旦我不使用缓存(注释以下行):

//cacher.put(lkey, imageData);
Run Code Online (Sandbox Code Playgroud)

内存使用率看起来很好:

在此输入图像描述

所以看起来缓存BufferedImage会导致内存泄漏.

然后我尝试转换BufferedImagebyte[]和缓存byte[]而不是对象本身.内存使用情况仍然正常.但是我发现Serialization,并DeserializationBufferedImage将花费太多时间.

所以我想知道你们有没有图像缓存的经验?


更新:

既然有这么多人说没有内存泄漏但是我的cacher使用了太多的内存,我不确定,但我试图缓存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);
Run Code Online (Sandbox Code Playgroud)

和内存使用情况:

在此输入图像描述

相同的代码,相同的操作.

Gla*_*boz 0

不确定您正在使用什么缓存 API 或您的请求中的实际值是什么。然而,根据 VisualVM,我认为 String 对象正在泄漏。另外,正如您提到的,如果关闭缓存,问题就可以解决。

考虑以下代码片段的摘录。

    String lkey = layer + "-" + coordinate;
    BufferedImage imageData = cacher.get(lkey);
Run Code Online (Sandbox Code Playgroud)

现在,您需要考虑此代码的一些事项。

  • 您可能每次都会为 lkey 获取新的字符串对象
  • 您的缓存没有上限且没有驱逐策略(例如LRU)
  • Cacher 不是执行 String.equals() 而是执行 == ,因为这是新的字符串对象,所以它们永远不会匹配,每次都会导致新条目