Подтвердить что ты не робот

JDK 1.7: "Слишком много открытых файлов" из-за семафоров POSIX?

Я просмотрел другие подобные вопросы по 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

В man-странице указано, что тип PSXSEM - это POSIX-семафор. Вы знаете, для чего JDK использует POSIX-семафоры? BTW, приложение представляет собой однопоточное приложение командной строки на данный момент.

Потенциально полезный фон: я впервые заметил это после перехода на JDK 1.7 на Mac OS X 10.7.3:

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)

Обновление: repointing $JAVA_HOME в JDK 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 по-другому?

4b9b3361

Ответ 1

Мне удалось проследить его до этого блока кода:

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 для этой проблемы.

Ответ 2

Я нашел другую причину. Кажется, что метод toRGB() ColorSpace пропускает семафоры. Выполнение следующего кода:

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)

Выйдет из системных файлов.

EDIT: уже отправил в Oracle

Ответ 3

Обновление: Как отмечали другие пользователи, 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, отчет о backport указывает, что он был исправлен в 1.7.0_06.

Итак, если вы видите это на 1.7.0_04 или 05, обновление по крайней мере 1.7.0_06 позаботится об этой проблеме.