首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >缓存BufferedImage时可能出现内存泄漏

缓存BufferedImage时可能出现内存泄漏
EN

Stack Overflow用户
提问于 2014-07-10 02:58:07
回答 3查看 3.9K关注 0票数 10

我们有一个为图像服务的应用程序,为了加快响应时间,我们直接将BufferedImage缓存在内存中。

代码语言:javascript
运行
复制
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;
    }
}

用法:

代码语言:javascript
运行
复制
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+内存被保留。事实上,通过"firebug“,我可以确保生成的映像小于1Kb。所以我认为记忆的使用是不健康的。

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

代码语言:javascript
运行
复制
//cacher.put(lkey, imageData);

内存使用情况看起来很好:

因此,缓存的BufferedImage似乎会导致内存泄漏。

然后,我尝试将BufferedImage转换为byte[],并缓存byte[]而不是对象本身。内存的使用仍然是正常的。然而,我发现SerializationDeserializationBufferedImage需要花费太多的时间。

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

最新情况:

因为有那么多人说没有内存泄漏,但是我的传道者使用了太多的内存,我不确定,但是我尝试过直接缓存byte[]而不是BufferedImage,而且内存的使用看起来很好。我无法想象322张图片会占用1.5G+内存,事件就像@BrettOkken说的那样,总大小应该是(256 * 256 * 4byte) * 322 / 1024 / 1024 = 80M,远远小于1Gb。

刚才,我更改为缓存byte并再次监视内存,代码更改如下:

代码语言:javascript
运行
复制
BufferedImage ig = generateImage(layer,coordinate rwidth, rheight);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(ig, "png", bos);
imageData = bos.toByteArray();
tileCacher.put(lkey, imageData);

以及内存的使用:

同样的代码,同样的操作。

EN

回答 3

Stack Overflow用户

发布于 2014-07-15 01:43:27

请注意,这两个VisualVM屏幕截图都表明,4313个int我认为它是通过缓存的缓冲图像实例消耗的97.5%的内存不是在非缓存版本中使用的。

尽管您的PNG图像小于1K (按PNG格式压缩),但这个图像是由多个缓冲图像实例(未压缩)生成的。因此,不能将图像大小从浏览器直接关联到服务器上占用的内存。因此,这里的问题不是内存泄漏,而是缓存缓冲图像的未压缩层所需的内存量。

解决此问题的策略是调整缓存机制:

  • 如果可能的话,可以使用缓存层的压缩版本而不是原始映像。
  • 通过通过实例限制缓存大小或按使用的内存量来确保不会耗尽内存。使用LRU或LIRS缓存驱逐策略
  • 使用带有坐标和层的自定义key对象作为两个单独的变量,用equals/hashcode覆盖作为键。
  • 观察这种行为,如果您有太多的缓存丢失,那么您将需要更好的缓存策略,否则缓存可能会产生不必要的开销。
  • 我相信您正在缓存层,因为您期望层和坐标的组合,因此不能缓存最终图像,但根据请求的类型,您可能希望考虑该选项。
票数 3
EN

Stack Overflow用户

发布于 2014-07-14 22:42:29

不确定您正在使用的缓存API或请求中的实际值是什么。然而,基于可视化,在我看来,字符串对象正在泄漏。此外,正如您提到的,如果您关闭缓存,问题就解决了。

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

代码语言:javascript
运行
复制
    String lkey = layer + "-" + coordinate;
    BufferedImage imageData = cacher.get(lkey);

下面是一些代码需要考虑的事项。

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

Stack Overflow用户

发布于 2014-07-18 22:06:43

VisualVM是一个开始,但它并没有给出完整的情况。

在应用程序使用大量内存时,需要触发堆转储。您可以从VisualVM触发堆转储。如果将此vmarg添加到java进程中,也可以在OOME上自动完成此操作:

代码语言:javascript
运行
复制
 -XX:+HeapDumpOnOutOfMemoryError 

使用内存分析器工具打开和检查堆转储。

该工具非常有能力,可以帮助您遍历对象引用以发现:

  1. 什么是真正使用你的记忆。
  2. 为什么#1中的对象没有被垃圾收集。
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24666779

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档