JDK-6247845 : Exception during ImageIO.write() causes 'leak' leading to OutOfMemoryError
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.imageio
  • Affected Version: 1.4.2
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2005-03-30
  • Updated: 2011-02-16
  • Resolved: 2005-04-12
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.4.2_07"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_07-b05)
Java HotSpot(TM) Client VM (build 1.4.2_07-b05, mixed mode)

Same problem with 1.4.2_05.

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
Linux [hostname] 2.4.18.[hostname] #1 SMP Wed May 5 14:34:55 BST 2004 i686 unknown

A DESCRIPTION OF THE PROBLEM :
If ImageIO.write(RenderedImage im, String formatName, ImageOuputStream output) encounters an exception while writing the image to the stream then resources are not disposed of, eventually leading to an OutOfMemoryError being thrown.

Note that we are actually calling ImageIO.write(RenderedImage im, String formatName, OuputStream output) but this just wraps the output stream in an imageoutputstream and calls write(RenderedImage im, String formatName, ImageOuputStream output).

I have boiled this problem down to the suupplied test case.  However, this is affecting us in a servlet container environment where we are getting OutOfMemoryErrors.  This is because SocketExceptions are occurring while serving an image, meaning that ImageIO.write() is not disposing of resources that it is responsible for.  It seems this could be simply fixed by putting the writer.dispose() in a finally block in ImageIO.write(RenderedImage, String, ImageOuputStream).  I believe SocketExceptions occur when people browsing the web terminate the connection with the web server by, for example, stopping page download or going to another page before all images have downloaded.

This problem is causing frequent unnecessary restarts of our servlet container.  Not good.  I have marked the severity as "Some progress is possible without resolving this bug" because the two higher severities would imply that we are unable to do any work at all until this is fixed whereas of course there is plenty of other work on our system for us to do.  However, this is causing us pain in our live server environment and is therefore important to us.

Note that in the supplied test case I have created an ExplodingOutputStream which recreates what is going on in our servlet container - i.e. an exception is thrown during stream.write().

Thanks for your attention.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the supplied test case, supplying a JPEG filename on the command line.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
An OutOfMemoryError should not occur.  The program should run to completion (if one was patient enough to wait!).
ACTUAL -
After 300-400 iterations of writing the image to the ExplodingOutputStream, an OutOfMemoryError is thrown.

Iterations: 0 mem: 1822 free, 1984 total, 65088 max.
Iterations: 100 mem: 12864 free, 29532 total, 65088 max.
Iterations: 200 mem: 25757 free, 58692 total, 65088 max.
Iterations: 300 mem: 15887 free, 65088 total, 65088 max.
Exception in thread "main" java.lang.OutOfMemoryError

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.OutOfMemoryError

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package test;

import java.awt.image.BufferedImage;
import java.io.*;
import java.net.SocketException;

import javax.imageio.ImageIO;

/**
 * Demonstrate memory leak when exception is thrown up through ImageIO.write.
 */
public class ImageIOLeak {

    public static void main(String[] args) throws Exception {
        if (args.length < 1) {
            System.out.println("Please provide a JPEG filename on the command line.");
            System.exit(1);
        }

        for (int i = 0; i < 10000; i++) {
            if (i % 100 == 0) {
                long free = Runtime.getRuntime().freeMemory() / 1024;
                long max = Runtime.getRuntime().maxMemory() / 1024;
                long total = Runtime.getRuntime().totalMemory() / 1024;
                System.out.println("Iterations: " + i + " mem: " + free + " free, " + total + " total, " + max + " max.");
            }

            try {
                readAndWriteImage(args[0]);
            }
            catch (SocketException e) {
                // Ignore.
            }
        }

        System.exit(0);
    }

    private static void readAndWriteImage(String filename) throws IOException {
        InputStream originalStream = new FileInputStream(filename);
//        OutputStream outStream = new ByteArrayOutputStream();
        OutputStream outStream = new ExplodingOutputStream();
        String type = "JPEG";
        ImageIO.setUseCache(false);

        BufferedImage original = ImageIO.read(originalStream);
        if (original == null) {
            throw new IOException("Unable to read from InputStream");
        }

        try {
            ImageIO.write(original, type, outStream);
        }
        finally {
            outStream.flush();
            outStream.close();
            originalStream.close();
        }
    }

    private static class ExplodingOutputStream extends OutputStream {

        public synchronized void write(int b) throws IOException {
            throw new SocketException("Kaboom!");
        }
    }
}


---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
None implemented yet - we just have to restart the servlet container (which we do automatically).

We are aware of ways to mitigate the effect on the servlet container and will be implementing these shortly (e.g. caching images so that ImageIO.write() is called much less often), but still believe the underlying bug in ImageIO.write() needs to be addressed.
###@###.### 2005-03-30 06:43:23 GMT

Comments
EVALUATION I believe this issue was fixed in 5.0 under bugids 4827358, 4867874, and 4868479 (basically, we do a much better job disposing native resources than we did before, thus avoiding any memory "leaks"). If the customer is unable to move to JDK 5.0, then they will have to file an escalation to have it fixed in a JDK 1.4.2 update release. Otherwise, I'm not sure why this bug report is open. Marking incomplete pending details from the submitter. ###@###.### 2005-04-06 22:10:57 GMT The submitter confirmed that this bug report can be closed since the issue was fixed in JDK 5.0. Closing as a dup of 4827358. ###@###.### 2005-04-12 18:53:07 GMT
06-04-2005