17

我已经查看了关于 SO 的其他类似问题,但它们似乎是由其他问题引起的。

首先,我确保我明智地关闭了所有文件句柄,然后我过去常常lsof -p <pid of java>查看我的文件列表。

它在我的整个运行时保持不变,但我会定期获得大约 10,000 个条目,lsof如下所示:

COMMAND   PID USER   FD     TYPE DEVICE  SIZE/OFF     NODE NAME
                                      ...
java    36809  smm *235r  PSXSEM              0t0          kcms00008FC901624000
java    36809  smm *236r  PSXSEM              0t0          kcms00008FC901624000
java    36809  smm *237r  PSXSEM              0t0          kcms00008FC901624000
java    36809  smm *238r  PSXSEM              0t0          kcms00008FC901624000
java    36809  smm *239r  PSXSEM              0t0          kcms00008FC901624000

手册页说PSXSEM类型是 POSIX 信号量。任何线索 JDK 使用 POSIX 信号量做什么?顺便说一句,该应用程序目前是一个单线程命令行应用程序。

潜在有用的背景:我在 Mac OS X 10.7.3 上升级到 JDK 1.7 后首先注意到这一点:

java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)

更新:重新指向$JAVA_HOMEJDK 1.6 似乎是解决该问题的方法。

java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b04-415-11M3635)
Java HotSpot(TM) 64-Bit Server VM (build 20.6-b01-415, mixed mode)

JDK 1.7 有何不同?

4

3 回答 3

7

我能够将其追溯到这段代码:

BufferedImage image = null;
ImageInputStream stream = null;
try {
    stream = new FileImageInputStream(file);
    image = ImageIO.read(stream);

} catch (Exception ex) {
    log.error("Image could not be read: "+file.getPath());

} finally {
    // ImageIO closes input stream unless null is returned
    // http://docs.oracle.com/javase/7/docs/api/javax/imageio/ImageIO.html#read(javax.imageio.stream.ImageInputStream)
    if (stream != null && image == null) {
        try {
            stream.close();
        } catch (IOException ex) {
            log.error("ERROR closing image input stream: "+ex.getMessage(), ex);
        }
    }
}

JavaDocs 特别指出此方法(与其他方法不同)会自动关闭流。事实上,当您尝试手动关闭它时,它会抛出一个异常“关闭”。我正在使用这个重载,因为另一个说它ImageInputStream无论如何都会将它包装起来,所以我想我会节省一些工作。

更改块以使用正常FileInputStream修复泄漏:

BufferedImage image = null;
InputStream stream = null;
try {
    stream = new FileInputStream(file);
    image = ImageIO.read(stream);

} catch (Exception ex) {
    log.error("Image could not be read: "+file);

} finally {
    if (stream != null) {
        try {
            stream.close();
        } catch (IOException ex) {
            log.error("ERROR closing image input stream: "+ex.getMessage(), ex);
        }
    }
}

在我看来,这似乎是 JDK 1.7 中的一个错误,因为 1.6 在这里运行良好。

更新:我刚刚就这个问题向 Oracle提交了一份错误报告。

于 2012-05-04T04:30:11.493 回答
4

我找到了另一个原因。似乎 ColorSpace 的 toRGB() 方法正在泄漏信号量。运行以下代码:

import java.awt.color.ColorSpace;
public class Test
{
    public static void main(String[] args) throws Throwable {
        final ColorSpace CIEXYZ = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
        for(int i = 0; i < 10000000; i++) {
            CIEXYZ.toRGB(new float[] {80f, 100, 100});
        }
    }
}

和:

java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)

会让你离开系统文件。

编辑:已经向Oracle提交了错误报告

于 2012-05-09T20:00:20.723 回答
4

更新:正如其他用户所说,ImageIO 在 1.7.0_04 和 1.7.0_05 中泄漏了信号量。用户juancn和用户mckamey的错误报告已被标记为已修复并已关闭(谢谢大家!)。说明:

此修复报告了 macosx 上的文件处理程序泄漏。它提到了泄漏处理程序的两种方法:通过 ImageIO.read(ImageInputStream) 和通过信号量。

我没有观察到第一次泄漏:如果找到合适的阅读器,我们会显式关闭输入流,这足以(至少在 1.7.4 上)释放文件句柄。

但是,在信号量的情况下,我们会泄漏大量句柄:我们对 jpeg 图像的每一行执行颜色转换,并且每次创建一个信号量(因为我们看到系统上安装了 2 个或更多 CPU),然后我们减少单独的数量任务减少到 1(因为我们有单个扫描线要处理),因此永远不会取消链接信号量。

Linux 系统上也存在同样的问题,但程度较轻,因为我们为每个命名信号量占用单个文件句柄,而在 macosx 上,我们总是占用新的文件句柄。

建议的修复只是推迟命名信号量的创建,直到我们澄清单独任务的数量,所以,现在我们不为图像读取和简单的颜色转换(如 ColorSpace.toRGB())创建信号量。除此之外,现在我们使用 pSem 指针作为信号量销毁的触发器。

尽管他们的报告表明该修复程序在版本 8 中,但反向移植报告表明它已在 1.7.0_06 中修复。

因此,如果您在 1.7.0_04 或 05 上看到此问题,则至少更新到 1.7.0_06 即可解决此问题。

于 2013-05-21T19:14:41.360 回答