JAI create seems to leave file descriptors open
Asked Answered
K

2

3

I have some old code that was working until recently, but seems to barf now that it runs on a new server using OpenJDK 6 rather than Java SE 6.

The problem seems to revolve around JAI.create. I have jpeg files which I scale and convert to png files. This code used to work with no leaks, but now that the move has been made to a box running OpenJDK, the file descriptors seem to never close, and I see more and more tmp files accumulate in the tmp directory on the server. These are not files I create, so I assume it is JAI that does it.

Another reason might be the larger heap size on the new server. If JAI cleans up on finalize, but GC happens less frequently, then maybe the files pile up because of that. Reducing the heap size is not an option, and we seem to be having unrelated issues with increasing ulimit.

Here's an example of a file that leaks when I run this:

/tmp/imageio7201901174018490724.tmp

Some code:

// Processor is an internal class that aggregates operations
// performed on the image, like resizing
private byte[] processImage(Processor processor, InputStream stream) {
    byte[] bytes = null;
    SeekableStream s = null;
    try {
        // Read the file from the stream
        s = SeekableStream.wrapInputStream(stream, true);
        RenderedImage image = JAI.create("stream", s);
        BufferedImage img = PlanarImage.wrapRenderedImage(image).getAsBufferedImage();
        // Process image
        if (processor != null) {
            image = processor.process(img);
        }
        // Convert to bytes
        bytes = convertToPngBytes(image);
    } catch (Exception e){
       // error handling
    } finally  {
        // Clean up streams
        IOUtils.closeQuietly(stream);
        IOUtils.closeQuietly(s);
    }
    return bytes;
}

private static byte[] convertToPngBytes(RenderedImage image) throws IOException {
    ByteArrayOutputStream out = null;
    byte[] bytes = null;
    try {
        out = new ByteArrayOutputStream();
        ImageIO.write(image, "png", out);
        bytes = out.toByteArray();
    } finally {
        IOUtils.closeQuietly(out);
    }
    return bytes;
}

My questions are:

  1. Has anyone run into this and solved it? Since the tmp files created are not mine, I don't know what their names are and thus can't really do anything about them.
  2. What're some of the libraries of choice for resizing and reformatting images? I heard of Scalr - anything else I should look into?

I would rather not rewite the old code at this time, but if there is no other choice...

Thanks!

Kannada answered 29/8, 2013 at 17:33 Comment(0)
K
0

Found it!

So a stream gets wrapped by another stream in a different area in the code:

iis = ImageIO.createImageInputStream(stream);

And further down, stream is closed.

This doesn't seem to leak any resources when running with Sun Java, but does seem to cause a leak when running with Open JDK.

I'm not sure why that is (I have not looked at source code to verify, though I have my guesses), but that's what seems to be happening. Once I explicitly closed the wrapping stream, all was well.

Kannada answered 3/9, 2013 at 19:37 Comment(2)
so in this instance, you expicitly called iis.close() and that solved the problem?Profess
@Profess alas this was a long time ago, and I don't recall what I did. I agree that does seem to be what the answer implies, I just can't verify if that is indeed what I did. Sorry.Kannada
W
4

Just a comment on the temp files/finalizer issue, now that you seem to have solved the root of the problem (too long for a comment, so I'll post it as an answer... :-P):

The temp files are created by ImageIO's FileCacheImageInputStream. These instances are created whenever you call ImageIO.createImageInputStream(stream) and the useCache flag is true (the default). You can set it to false to disable the disk caching, at the expense of in-memory caching. This might make sense as you have a large heap, but probably not if you are processing very large images.

I also think you are (almost) correct about the finalizer issue. You'll find the following ´finalize´ method on FileCacheImageInputStream (Sun JDK 6/1.6.0_26):

protected void finalize() throws Throwable {
    // Empty finalizer: for performance reasons we instead use the
    // Disposer mechanism for ensuring that the underlying
    // RandomAccessFile is closed/deleted prior to garbage collection
}

There's some quite "interesting" code in the class' constructor, that sets up automatic stream closing and disposing when the instance is finalized (should client code forget to do so). This might be different in the OpenJDK implentation, at least it seems kind of hacky. It's also unclear to me at the moment exactly what "performance reasons" we are talking about...

In any case, it seems calling close on the ImageInputStream instance, as you now do, will properly close the file descriptor and delete the temp file.

Waftage answered 4/9, 2013 at 11:14 Comment(2)
thanks for the input. I admit I did not dig into the source code. I was also not aware of the cache settings. Much appreciated feedback.Kannada
Definitely some weirdness going on in, in my instance JNA was closing handles too early. Weird https://mcmap.net/q/1920313/-is-jai-closing-file-handles-too-early/32453Profess
K
0

Found it!

So a stream gets wrapped by another stream in a different area in the code:

iis = ImageIO.createImageInputStream(stream);

And further down, stream is closed.

This doesn't seem to leak any resources when running with Sun Java, but does seem to cause a leak when running with Open JDK.

I'm not sure why that is (I have not looked at source code to verify, though I have my guesses), but that's what seems to be happening. Once I explicitly closed the wrapping stream, all was well.

Kannada answered 3/9, 2013 at 19:37 Comment(2)
so in this instance, you expicitly called iis.close() and that solved the problem?Profess
@Profess alas this was a long time ago, and I don't recall what I did. I agree that does seem to be what the answer implies, I just can't verify if that is indeed what I did. Sorry.Kannada

© 2022 - 2024 — McMap. All rights reserved.