9

I have two Java applications that both use a ton of memory, and both use ImageIO.write(). So far, that is the only thing I have found in common between the two.

One resizes images in a loop. The other downloads images in a loop and saves them to disk. Here's the relevant code:

1)

for(File imageFile : imageFilesList)
{
    if(!stillRunning) return;

    File outputFile = new File(imageFile.getAbsolutePath().replace(sourceBaseFolder.getAbsolutePath(), destinationFolder.getAbsolutePath()));
    try
    {
        outputFile.mkdirs();
        BufferedImage inputImage = ImageIO.read(imageFile);
        BufferedImage resizedImage = ImageResizer.resizeImage(inputImage, maxHeight, maxWidth);
        ImageIO.write(resizedImage, "jpg", outputFile);
    }
    catch(IOException ex)
    {
        userInterface.displayMessageToUser("IOException ocurred while converting an image: " + ex.getLocalizedMessage());
        System.out.println(outputFile.getAbsolutePath());
        ex.printStackTrace();
        return;
    }
    imagesConverted++;
    userInterface.updateTotalConvertedImages(++convertedFiles);
}

2) (inside a loop)

try
{
    u = new URL(urlString);
    uc = u.openConnection();
    uc.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
    uc.connect();
    uc.getInputStream();
    in = uc.getInputStream();

    BufferedImage tempImage = ImageIO.read(in);

    String fileName = fn = ImageDownload.getFileName(u.getPath());
    fileName = outputDirString + FILE_SEPARATOR + fileName;
    while (new File(fileName).exists())
    {
        fileName = appendCopyIndicator(fileName);
    }

    ImageIO.write(tempImage, "jpg", new File(fileName));
    parent.notifyOfSuccessfulDownload(fn);
    in.close();
}
catch (FileNotFoundException ex)
{
    parent.notifyOfFailedDownload(fn);
}
catch (IOException ex)
{
    parent.handleException(ex);
}

In both cases, the program uses a lot of memory. Right around a gig of RAM. And it doesn't get freed up when the loop is over. In both cases I have a swing gui running. When the image saving is all done and the gui is just idling, the program is still using sometimes 1Gb+ of memory. I have gone so far as to set every variable not directly used by the swing gui to null after the loop. To no effect.

What am I missing?

Thanks in advance for your help.

More information: I just profiled application 1 in my IDE (Netbeans). I chose application one because it only deals with ImageIO (and not network IO), so it's a more controlled experiment.

While the program was doing its thing (resizing images in a loop), the Total Memory hovered between roughly 900,000,000 ~ 1,000,000,000 bytes, while the Used Memory fluxuated between roughly 30% and 50% of the Total Memory being used at a given moment.

And time spent in GC never went above 1%.

As soon as the actual resizing was finished and the program went into "idle", two things happened: 1) The Total Memory stopped fluxuating and stayed static at 1,044,054,016 bytes, and 2) the Used Memory dropped to ~14,000,000 bytes (14 mb).

So, it looks like the JVM is just not giving back memory space that it's no longer using.

Agree? Or am I misreading this result?

cutmancometh
  • 1,677
  • 3
  • 20
  • 28
  • 2
    Using large amount of memory is _not_ necessarily a memory leak. Does it keep growing and run out of memory? Is it that your memory requirement is around 1G? – Jayan Sep 10 '13 at 07:36
  • Consider posting an [SSCCE](http://sscce.org/); it will be easier for everybody to asess the problem you are having. – Viktor Seifert Sep 10 '13 at 07:51
  • Viktor, thanks, I'll put it in SSCCE! – cutmancometh Sep 10 '13 at 08:30
  • `uc.getInputStream(); in = uc.getInputStream();` looks strange, is that intentional? – Harald K Sep 10 '13 at 08:33
  • Yeah. That's needless. I borrowed the code from someone else, and just forgot to take that out. – cutmancometh Sep 10 '13 at 09:01
  • 1
    Do you mean that the VM doesn't give back memory to the OS? AFAIK the VM does not do that; once it has requested the memory from the OS it will not give it back. – Viktor Seifert Sep 10 '13 at 11:15
  • 1
    As a small remark, you really should call outputFile.close() in the first example. – Nikem Sep 10 '13 at 12:37
  • java.io.File does not have a close() method, as it does not represent an open file or file handle, but rather an abstract path to a file. I would close an output stream here, but I don't have one to close. It's a safe bet that ImageIO.write() uses an output stream somewhere in its call stack, but I don't have access to that. – cutmancometh Sep 10 '13 at 19:23
  • 1
    Also `inputFile` goes out of scope every time through the loop, so it gets garbage collected and reallocated each time through. I observed this in my profiling test because even when I'm batch-resizing 1000+ images in a loop, the number of live instances of java.io.File never goes above 90. – cutmancometh Sep 10 '13 at 19:25
  • Yeah. I looked at the source code for ImageIO.write(). It uses an ImageOutputStream, which does get closed in a `finally` clause. – cutmancometh Sep 10 '13 at 19:44

4 Answers4

8

I have something similar and when I look into the memory allocated I see several large blocks of memory (approx. 10-20 MB each) that will not be released. Looking inside them with a VirtualVM memory dump, it seems as if they are marked as "owned" by the imageIO JpegImageReader. In this case, GC will not clear them as they are marked as used by an external GNI call (the JpegImageReader). and theJpegImageReader is long gone, so they will just stay there.

In my case it happens randomly. I suspect it has to do when there are many calls in parallel (from multiple threads), or when there is an internal exception inside the ImageReader it will not release its memory, but not 100% sure. (and yes, I do flush the buffered images, dispose the reader, and close the streams. Does not seem to help).

Yishai S.
  • 101
  • 5
  • I came across this problem as well while using ImageIO inside a ServletContext and simply couldn't figure out what could be the problem, it definitely has nothing to do with concurrency since I used ImageIO only in one method that was synchronized. Ended up installing TwelveMonkeys (https://github.com/haraldk/TwelveMonkeys) and used custom code to make absolutely sure that ImageReader from TwelveMonkeys is always used in case the format is JPEG. Now my image data is garbage collected just fine and everything works. – juhovh Nov 17 '17 at 19:34
  • Tried that, didn't help for me because `com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader.read` just calls `com.sun.imageio.plugins.jpeg.JPEGImageReader.read` and drives the code where I think the leak is. This leak has been reported lots of times, seems nobody has a solution – Dave Griffiths Jan 09 '21 at 12:29
0

What am I missing? An understanding of how Java garbage collection works ;-)

Memory won't be freed straight away, it will only be released once garbage collection runs. You can call the GC explicitly if you like. Also your OS plays a part - e.g. Unix systems typically don't actually release the memory even if you do free it (in C++ terms).

John3136
  • 28,809
  • 4
  • 51
  • 69
  • Note that you cannot force the Java GC to run. Calling [System.gc()](http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#gc%28%29) explicitely suggests to the JVM to run the garbage collector, but you cannot be sure that you memory will be freed after your call. In practice, it varies based on your JVM. – Albert Iordache Sep 10 '13 at 07:44
  • I have a little more understanding of garbage collection than that. The heap space _never_ gets collected. I can leave it sitting for literally hours and it never gets freed up. Ever. I know I have no control over when it gets collected, but the JVM isn't _that_ inefficient that it will leave it sitting there that long. So, I really don't think it's as simple as, "oh it just hasn't been GCd yet". – cutmancometh Sep 10 '13 at 08:24
  • Oh, also I have tried filling up the rest of memory with other processes in the hopes that the OS would, like, send a signal to the JVM saying, "hey, are you really using that much heap space, 'cause we could really use some of it." Never happens. I've gotten up to around 95% of physical memory being used, at which point the whole OS basically stops responding, and the Java app in question doesn't give up any ground. And, yes, this is all after the image saving is done and the application is just idling. – cutmancometh Sep 10 '13 at 08:27
  • I haven't seen a mention of which OS you are on - seems you have a bit of a clue about the JVM and GC, so it could come down to how the OS handles memory. – John3136 Sep 10 '13 at 11:58
  • java version "1.7.0_25" Java(TM) SE Runtime Environment (build 1.7.0_25-b17) Java HotSpot(TM) 64-Bit Server VM (build 23.25-b01, mixed mode) – cutmancometh Sep 10 '13 at 19:37
0

You should use .flush() on the BufferedImageno longer usable.
For example, in your 2nd code:

try
{
    u = new URL(urlString);
    uc = u.openConnection();
    uc.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
    uc.connect();
    uc.getInputStream();
    in = uc.getInputStream();

    BufferedImage tempImage = ImageIO.read(in);

    String fileName = fn = ImageDownload.getFileName(u.getPath());
    fileName = outputDirString + FILE_SEPARATOR + fileName;
    while (new File(fileName).exists())
    {
        fileName = appendCopyIndicator(fileName);
    }

    ImageIO.write(tempImage, "jpg", new File(fileName));

    // release internal buffered memory
    tempImage.flush();

    parent.notifyOfSuccessfulDownload(fn);
    in.close();
}
johan d
  • 2,798
  • 18
  • 26
  • Calling `flush()` on a `BufferedImage` that has not been painted (blitted) onto native components is basically a no-op. It *does not* free any memory allocated on the heap. – Harald K Sep 10 '13 at 08:31
  • hmm. In my personal case, it solved my problem, an the bufferedImage was never blitted. I just red it (double for loop) in order to make an openGL texture. Without the flush, I had a (very) excessive memory usage. – johan d Sep 10 '13 at 08:36
  • It makes sense, as OpenGL is native. In this case, simply letting `tempImage` go out of scope and let the GC do it's job should be enough. – Harald K Sep 10 '13 at 08:43
  • I think I understand, but I didn't use any BufferedImage method to blit, I just red the pixeldata (then, theses ints where converted etc etc) – johan d Sep 10 '13 at 08:51
  • Adding .flush() to all BufferedImage instances after using them didn't help. – cutmancometh Sep 10 '13 at 09:16
  • Thank for your feedback, and also to haraldK for the explanation why. I hope you'll find ! – johan d Sep 10 '13 at 10:52
0

I believe you are missing Image#flush() in both cases in #1 and #2. I suggest you call flush() inside a finally clause. In #2, to be on the safer side, call in.close() inside a finally clause as well because if you hit an exception before that line, then it will most likely not be closed based on the code snippet you pasted.

Ian
  • 691
  • 3
  • 9
  • 1
    Calling `flush()` on a `BufferedImage` that has not been painted (blitted) onto native components is basically a no-op. It *does not* free any memory allocated on the heap. Agree about #2 though. :-) – Harald K Sep 10 '13 at 08:29
  • Adding .flush() to all BufferedImage instances after using them didn't help. – cutmancometh Sep 10 '13 at 09:14