JAI创建似乎会保留文件描述符。

3

我有一些旧代码,之前可以正常工作,但现在在使用OpenJDK 6的新服务器上运行时似乎出了问题,而不是Java SE 6。

问题似乎围绕着JAI.create展开。我有一些jpeg文件,我将它们缩放并转换为png文件。这段代码以前可以正常工作且没有泄漏,但现在在迁移到运行OpenJDK的服务器上后,文件描述符似乎永远不会关闭,并且我看到越来越多的tmp文件在服务器的tmp目录中积累。这些不是我创建的文件,所以我认为是JAI创建的。

另一个原因可能是新服务器上较大的堆大小。如果JAI在finalize时进行清理,但GC发生的频率较低,则可能会因此导致文件积累。减小堆大小不是一个选项,而我们似乎遇到了与增加ulimit无关的问题。

以下是运行此代码时出现泄漏的文件示例:

/tmp/imageio7201901174018490724.tmp

一些代码:

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

我的问题是:
  1. 有人遇到过这个问题并解决了吗?由于创建的临时文件不是我的,所以我不知道它们的名称,因此无法处理它们。
  2. 哪些图像调整和重新格式化库是首选?我听说过Scalr - 还有其他什么值得我研究的吗?

我目前不想重写旧代码,但如果没有其他选择......

谢谢!

2个回答

4

关于临时文件/终结器问题的评论,现在看来你已经解决了根本问题(太长了无法作为评论,所以我将其发表为答案... :-P):

临时文件是由ImageIO的FileCacheImageInputStream创建的。每当调用ImageIO.createImageInputStream(stream)并且useCache标志为true(默认情况下)时,就会创建这些实例。您可以将其设置为false以禁用磁盘缓存,代价是内存缓存。如果您有很大的堆,这可能是有意义的,但如果您正在处理非常大的图像,则可能不是。

我还认为您(几乎)正确地解决了终结器问题。您会在FileCacheImageInputStream上找到以下´finalize´方法(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
}

在这个类的构造函数中,有一些相当“有趣”的代码,该代码设置了自动流关闭和处理程序的功能,以便在实例被终止时(如果客户端代码忘记这样做)进行清理。至少在OpenJDK实现中可能会有所不同,因为它似乎有点像黑客技巧。目前对我来说还不清楚我们正在谈论什么“性能原因”...

无论如何,现在调用ImageInputStream实例上的close方法,将适当地关闭文件描述符并删除临时文件。


感谢您的输入。我承认我没有深入研究源代码。我也不知道缓存设置。非常感谢您的反馈。 - MrSilverSnorkel
1
在编程中肯定会出现一些奇怪的问题,比如在我的实例中,JNA 关闭句柄太早了。很奇怪。http://stackoverflow.com/q/38444752/32453 - rogerdpack

0

找到了!

因此,在代码的另一个区域,流被另一个流包装:

iis = ImageIO.createImageInputStream(stream);

而且在下面,流已经关闭了。

当使用Sun Java运行时,似乎不会泄漏任何资源,但是当使用Open JDK运行时,似乎会导致泄漏。

我不确定为什么会这样(虽然我猜测过,但没有查看源代码进行验证),但这似乎就是发生的情况。一旦我明确地关闭了包装流,一切都好了。


那么在这种情况下,您明确调用了 iis.close() 并解决了问题? - rogerdpack
@rogerdpack 哎呀,这是很久以前的事了,我不记得我当时做了什么。我同意答案似乎是在暗示那个,只是我无法验证我是否真的这样做了。抱歉。 - MrSilverSnorkel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接