Possible memory leak when caching BufferedImage

We have an application that serves images to speed up response time, we cache 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; } } 

using:

 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: a registered provider is one instance.

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

Then I use visualvm to check for memory usage:

enter image description here

Even I Perform GC manually, memory cannot be freed.

Although only 300 + BufferedImage used for caching and 20M+ memory is used, 1.3G+ memory is 1.3G+ . In fact, through "firebug" I can make sure that the generated image is less than 1Kb . Therefore, I think that using memory is not healthy.

As soon as I will not use the cache (comment the following line):

 //cacher.put(lkey, imageData); 

Memory usage looks good:

enter image description here

It seems that the cached BufferedImage is causing a memory leak.

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

So, I wonder if you have experience caching images?


update:

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

And only now I go to the byte cache and track the memory again, the codes change as follows:

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

And memory usage:

enter image description here

Same codes, same action.

+7
java caching memory-leaks bufferedimage
source share
3 answers

Note that in both screenshots of VisualVM, 97.5% memory is consumed by 4,313 instances of int [] (which I assume is a cached buffer image) is not consumed in the cached version.

97.5% Memory Consumption

Although you have an image smaller than 1 KB in size (which is compressed in accordance with the PNG format), this single image is generated from multiple instances of a buffered image (which is not compressed). Therefore, you cannot directly relate the size of the image with the browser to the memory occupied on the server. Thus, the problem is not a memory leak, but the amount of memory needed to cache these uncompressed layers of buffered images.

The strategy for solving this problem is to configure the caching mechanism:

  • If possible, use a compressed version of the cached layers instead of the raw image.
  • Make sure you never run out of memory by limiting the cache size by case or by the amount of memory used. Use the LRU or LIRS cache eviction policy.
  • Use a custom key object with a coordinate and a layer as two separate variables, overriding with equals / hashcode to use as a key.
  • Watch the behavior, and if you have too many misses in the cache, then you will need a better caching strategy, or the cache may be unnecessary.
  • I believe that you are caching layers since you expect a combination of layer and coordinate and therefore cannot cache the final images, but depending on the type of sample requests you expect, you might want to consider this option if possible.
+3
source share

Not sure which caching API you are using or what actual values ​​are in your request. However, based on visualvm, it seems to me that String objects are leaking. Also, as you said, if you disable caching, the problem will be solved.

Consider extracting a piece of code below.

  String lkey = layer + "-" + coordinate; BufferedImage imageData = cacher.get(lkey); 

Now a few things you can consider for this code.

  • You can get new string objects for lkey every time
  • Your cache has no upper limit with and without seeding policy (e.g. LRU)
  • Cacher instead of executing String.equals () does ==, and since these are new string objects that they never match, calling a new record every time
0
source share

VisualVM is a start, but it does not give a full image.

You need to call a bunch of dump while the application uses a large amount of memory. You can run a bunch of heaps from VisualVM. This can also be done automatically on OOME if you add this vmarg to the java process:

  -XX:+HeapDumpOnOutOfMemoryError 

Use the Memory Analysis Tool to open and check the heap dump.

The tool is quite capable and can help you find references to objects to detect:

  • What actually uses your memory.
  • Why objects from # 1 are not garbage collected.
0
source share

All Articles