Java IOException «Слишком много открытых файлов»

#java #jmeter #jmeter-plugins

#java #file-io

Вопрос:

Я выполняю некоторый файловый ввод-вывод с несколькими файлами (так уж получилось, что записываю в 19 файлов). После записи в них несколько сотен раз я получаю Java IOException : Too many open files . Но на самом деле у меня открыто только несколько файлов одновременно. В чем здесь проблема? Я могу проверить, что записи были успешными.

Комментарии:

1. Поместите комментарий в функцию, которая откроет файлы, и убедитесь, что вы открываете каждый из них только один раз.

2. Откуда вы знаете, что эта ошибка возникает из-за одного из этих 19? Сама Java или используемая вами платформа могут открывать файлы.

3. опубликуйте свой код. Возможно, вы открываете их внутри цикла, поэтому это происходит неоднократно.

4. Если вы не закроете файлы самостоятельно и просто предоставите сборщику мусора очистить их за вас, то для этого потребуется много времени. Тем временем вы заняты открытием большего количества файлов и, прежде чем вы это осознаете, вы достигли предела.

Ответ №1:

В Linux и других UNIX / UNIX-подобных платформах операционная система устанавливает ограничение на количество дескрипторов открытых файлов, которые процесс может иметь в любой момент времени. В прежние времена это ограничение составляло 1 и было относительно небольшим. В наши дни это намного больше (сотни / тысячи) и зависит от «мягкого» настраиваемого ограничения ресурсов для каждого процесса. (Посмотрите ulimit встроенную оболочку …)

Ваше Java-приложение, должно быть, превышает ограничение по файловому дескриптору для каждого процесса.

Вы говорите, что у вас открыто 19 файлов, и что после нескольких сотен повторений вы получаете IOException со словами «открыто слишком много файлов». Теперь это конкретное исключение может произойти ТОЛЬКО при запросе нового файлового дескриптора; т. е. когда вы открываете файл (или канал, или сокет). Вы можете убедиться в этом, распечатав stacktrace для IOException.

Если ваше приложение не запускается с небольшим ограничением ресурсов (что кажется маловероятным), из этого следует, что оно должно неоднократно открывать файлы / сокеты / каналы и не закрывать их. Выясните, почему это происходит, и вы должны быть в состоянии выяснить, что с этим делать.

К вашему сведению, следующий шаблон — это безопасный способ записи в файлы, который гарантированно не приведет к утечке файловых дескрипторов.

 Writer w = new FileWriter(...);
try {
    // write stuff to the file
} finally {
    try {
        w.close();
    } catch (IOException ex) {
        // Log error writing file and bail out.
    }
}
  

1 — Жестко, как при компиляции в ядро. Изменение количества доступных слотов fd потребовало перекомпиляции … и может привести к уменьшению объема памяти, доступной для других целей. В те дни, когда Unix обычно запускался на 16-разрядных машинах, эти вещи действительно имели значение.

Обновить

Способ Java 7 более лаконичен:

 try (Writer w = new FileWriter(...)) {
    // write stuff to the file
} // the `w` resource is automatically closed 
  

ОБНОВЛЕНИЕ 2

По-видимому, вы также можете столкнуться с «слишком много открытых файлов» при попытке запустить внешнюю программу. Основная причина, как описано выше. Однако причина, по которой вы сталкиваетесь с этим в exec(...) , заключается в том, что JVM пытается создать файловые дескрипторы «pipe», которые будут подключены к стандартному вводу / выводу / ошибке внешнего приложения.

Комментарии:

1. Я бы рекомендовал использовать IOUtils.closeQuietly()

2. Я бы тоже это использовал. Но я бы не рекомендовал добавлять эту зависимость ТОЛЬКО для того, чтобы получить метод, который вы можете написать самостоятельно за 2 минуты.

3. Действительно, в 2016 году я бы настоятельно рекомендовал использовать try with resources ; то есть «Java 7 way». Если вы все еще используете Java 6 или более раннюю версию, вам следует выполнить обновление. Java 6 не обслуживалась уже 4 года.

4. Я был так счастлив, когда прочитал на странице документации IOUtils о closeQuietly (Закрываемый… метод closeables), который говорит, "Deprecated. As of 2.6 removed without replacement. Please use the try-with-resources statement or handle suppressed exceptions manually."

5. @computingfreak — Да. Начиная с Java 7, вам больше не нужно close() делать это явно. IOUtils Устаревание просто пытается заставить делать вещи более современным способом… и будьте счастливее. Смотрите мое первое ОБНОВЛЕНИЕ.

Ответ №2:

Для UNIX:

Как предложил Стивен Си, изменение максимального значения файлового дескриптора на более высокое позволяет избежать этой проблемы.

Попробуйте посмотреть на текущую емкость вашего файлового дескриптора:

    $ ulimit -n
  

Затем измените ограничение в соответствии с вашими требованиями.

    $ ulimit -n <value>
  

Обратите внимание, что это просто изменяет ограничения в текущей оболочке и любом дочернем процессе-потомке. Чтобы изменение «закрепилось», вам нужно поместить его в соответствующий сценарий оболочки или файл инициализации.

Комментарии:

1. Это позволяет избежать проблемы только до тех пор, пока ваше приложение не достигнет нового более высокого предела. ИМО, это не очень хорошее решение.

Ответ №3:

Очевидно, что вы не закрываете свои файловые дескрипторы перед открытием новых. Вы используете Windows или Linux?

Комментарии:

1. Если он действительно открывает 19 файлов и имеет массив с файловыми дескрипторами, то с ним все должно быть в порядке, но я думаю, что на самом деле он открывает больше, чем думает.

2. AFAIK, в Windows нет этой проблемы; например, на моем XP SP3 я смог без труда просмотреть все дерево исходных текстов файлов CXF (около 15K), не закрывая ни один из открытых файлов.

3. Я гарантирую, что Windows имеет ограничение на дескриптор файла. Это просто может быть значение по умолчанию 65000

Ответ №4:

Хотя в большинстве общих случаев ошибка совершенно очевидна в том, что дескрипторы файлов не были закрыты, я только что столкнулся с экземпляром с JDK7 в Linux, который ну … достаточно отредактирован, чтобы объяснить здесь.

Программа открыла FileOutputStream (fos), BufferedOutputStream (bos) и DataOutputStream (dos). После записи в dataoutputstream dos был закрыт, и я думал, что все прошло нормально.

Однако внутри dos попытался сбросить bos, который вернул ошибку Disk Full. Это исключение было поглощено DataOutputStream, и, как следствие, базовый bos не был закрыт, следовательно, fos все еще был открыт.

На более позднем этапе этот файл был переименован из (что-то с .tmp) в его настоящее имя. Таким образом, средства отслеживания файловых дескрипторов Java потеряли исходный файл .tmp, хотя он все еще был открыт!

Чтобы решить эту проблему, мне пришлось сначала самостоятельно очистить DataOutputStream, извлечь IOException и самостоятельно закрыть FileOutputStream.

Я надеюсь, что это кому-то поможет.

Комментарии:

1. Это не имеет никакого смысла вообще. DataOutputStream не проглатывает исключения. FilterOutputStream.close() выполняет, но не таким образом, который подавлял бы закрытие. Часть о «трекерах файловых дескрипторов Java», какими бы они ни были, «потерянный трек исходного файла .tmp» лишена определенного значения.

Ответ №5:

Если вы видите это в автоматических тестах: лучше всего правильно закрывать все файлы между тестовыми запусками.

Если вы не уверены, какие файлы вы оставили открытыми, хорошее место для начала — это вызовы «open», которые генерируют исключения! 😄

Если у вас есть дескриптор файла, который должен быть открыт ровно до тех пор, пока его родительский объект активен, вы могли бы добавить finalize метод для родительского файла, который вызывает close для дескриптора файла. И вызов System.gc() между тестами.

Ответ №6:

Недавно у меня была программа пакетной обработки файлов, я, конечно, закрыл каждый файл в цикле, но ошибка все еще там.

И позже я решил эту проблему, тщательно собирая мусор из каждой сотни файлов:

 int index;
while () {
    try {
        // do with outputStream...
    } finally {
        out.close();
    }
    if (index   % 100 = 0)
        System.gc();
}
  

Комментарии:

1. Извините, но вы ошибаетесь. Вы можете подумать , что вы явно закрыли все потоки, но где-то в вашей программе есть путь выполнения, который приводит к тому, что потоки не закрываются. Запуск GC приводит к завершению «потерянных» потоков. Вызовы завершения потока this.close() (IIRC).

2. Полезно знать, что gc () закрывает потоки.

3. @StefanReich Это не так. Это закрывает файловые дескрипторы. Потоки останутся незаполненными.