Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible memory leak when caching BufferedImage

We have an application which serve images, to speed up the response time, we cache the BufferedImage directly in memory.

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;
    }
}

usage:

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

    }
}

Note:the provider filed is a single instance.

However it seems that there is a possible memory leak because I will get Out Of Memory exception when the application keep running for about 2 minutes.

Then I use visualvm to check the memory usage:

enter image description here

Even I Perform GC manually, the memory can not be released.

And Though there are only 300+ BufferedImage cached, and 20M+ memory are used, 1.3G+ memory are retained. In fact, through "firebug" I can make sure that a generate image is less than 1Kb. So I think the memory usage is not healthy.

Once I do not use the cache (comment the following line):

//cacher.put(lkey, imageData);

The memory usage looks good:

enter image description here

So it seem that the cached BufferedImage cause the memory leak.

Then I tried to transform the BufferedImage to byte[] and cache the byte[] instead of the object itself. And the memory usage is still normal. However I found the Serialization and Deserialization for the BufferedImage will cost too much time.

So I wonder if you guys have any experience of image caching?


update:

Since there are so many people said that there is no memory leak but my cacher use too many memory, I am not sure but I have tried to cache byte[] instead of BufferedImage directly, and the memory use looks good. And I can not imagine 322 image will take up 1.5G+ memory,event as @BrettOkken said, the total size should be (256 * 256 * 4byte) * 322 / 1024 / 1024 = 80M, far less than 1Gb.

And just now,I change to cache the byte and monitor the memory again, codes change like this:

BufferedImage ig = generateImage(layer,coordinate rwidth, rheight);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(ig, "png", bos);
imageData = bos.toByteArray();
tileCacher.put(lkey, imageData);

And the memory usage:

enter image description here

Same codes, same operation.

like image 462
hguser Avatar asked Jul 10 '14 02:07

hguser


People also ask

What could be the possible cause of memory leaks?

A memory leak may also happen when an object is stored in memory but cannot be accessed by the running code (i.e. unreachable memory). A memory leak has symptoms similar to a number of other problems and generally can only be diagnosed by a programmer with access to the program's source code.

How do you identify memory leaks?

Running out of memory is the simplest way to identify a memory leak, and it's also the most common approach to uncovering one. That's also the most inconvenient way to find a leak. You'll probably notice your system slowing down before you run out of RAM and crash your application.

What are the common causes of performance issues and memory leaks?

Use of finalizers is yet another source of potential memory leak issues. Whenever a class' finalize() method is overridden, then objects of that class aren't instantly garbage collected. Instead, the GC queues them for finalization, which occurs at a later point in time.


1 Answers

Note from both VisualVM screenshots that 97.5% memory consumed by 4,313 instances of int[] (Which I assume is by cached buffered image) is not consumed in non-cached version.

97.5% Memory Consumption

Although you have a less than 1K PNG image (which is compressed as per PNG format), this single image is being generated out of multiple instances of buffered image (which is not compressed). Hence you cannot directly co-relate image size from browser to memory occupied on server. So issue here is not memory leak but amount of memory required to cache this uncompressed layers of buffered images.

Strategy to resolve this is to tweak your caching mechanism:

  • If possible use compressed version of layers cached instead of raw images
  • Ensure that you will never run out of memory by limiting cache size by instances or by amount of memory utilized. Use either LRU or LIRS cache eviction policy
  • Use custom key object with coordinate and layer as two separate variables overriding with equals/hashcode to use as key.
  • Observe the behavior and if you have too many cache misses then you will need better caching strategy or cache may be unnecessary overhead.
  • I believe you are caching layers as you expect combinations of layer and coordinates and hence cannot cache final images but depending on kind of pattern of requests you expect you may want to consider that option if possible
like image 156
Gladwin Burboz Avatar answered Oct 14 '22 02:10

Gladwin Burboz