Java IOException: "Слишком много открытых файлов"
Я выполняю операции ввода-вывода с несколькими файлами (в данном случае, записываю в 19 файлов). После того как я записываю данные в файлы несколько сотен раз, я получаю исключение Java IOException: Слишком много открытых файлов. Однако на самом деле у меня открыто всего лишь несколько файлов одновременно. В чем может быть проблема? Я могу подтвердить, что записи были успешными.
5 ответ(ов)
На Linux и других UNIX/UNIX-подобных платформах операционная система устанавливает лимит на количество открытых дескрипторов файлов, которые процесс может иметь в любой момент времени. Раньше этот лимит был фиксированным и относительно маленьким. В настоящее время он значительно увеличен (сотни или тысячи) и подлежит настройке с помощью "мягкого" лимита ресурсов для каждого процесса (можете ознакомиться с встроенной командой ulimit в shell...).
Ваше Java-приложение, похоже, превышает лимит на дескрипторы файлов для процесса.
Вы упоминаете, что у вас открыто 19 файлов, и после нескольких попыток вы получаете IOException с сообщением "слишком много открытых файлов". Это исключение может возникнуть ТОЛЬКО в том случае, если запрашивается новый дескриптор файла; то есть, когда вы открываете файл (или канал, или сокет). Вы можете это проверить, выведя стек вызовов для данного IOException.
Если ваше приложение не запускается с маленьким лимитом ресурсов (что кажется маловероятным), это значит, что оно многократно открывает файлы/сокеты/каналы и не закрывает их. Найдите причину этого поведения, и вы сможете разобраться, что с этим делать.
К вашему сведению, следующий паттерн является безопасным способом записи в файлы, который гарантирует отсутствие утечки дескрипторов файлов:
Writer w = new FileWriter(...);
try {
// запись данных в файл
} finally {
try {
w.close();
} catch (IOException ex) {
// Логируем ошибку записи файла и завершаем выполнение.
}
}
UPDATE
Способ, предложенный в Java 7, более лаконичен:
try (Writer w = new FileWriter(...)) {
// запись данных в файл
} // ресурс `w` автоматически закрывается
UPDATE 2
Кстати, вы также можете столкнуться с исключением "слишком много открытых файлов", пытаясь запустить внешнюю программу. Основная причина та же, что и описана выше. Однако вы получаете это исключение в exec(...) потому, что JVM пытается создать "канальные" дескрипторы файлов, которые будут подключены к стандартному потоку ввода/вывода/ошибок внешнего приложения.
Для UNIX:
Как уже предложил Стивен С., увеличение максимального значения дескрипторов файлов помогает избежать данной проблемы.
Сначала проверьте текущую емкость дескрипторов файлов:
$ ulimit -n
Затем измените лимит в соответствии с вашими требованиями:
$ ulimit -n <значение>
Имейте в виду, что это изменение касается только текущей оболочки и всех дочерних процессов. Чтобы сделать изменение постоянным, вам нужно добавить его в соответствующий скрипт оболочки или файл инициализации.
Вы, очевидно, не закрываете свои дескрипторы файлов перед открытием новых. Вы на Windows или Linux?
Хотя в большинстве случаев ошибка довольно очевидна, так как файловые дескрипторы не были закрыты, я столкнулся с ситуацией в JDK7 на Linux, которая достаточно запутанная, чтобы о ней рассказать.
Программа открывала FileOutputStream (fos), BufferedOutputStream (bos) и DataOutputStream (dos). После записи в DataOutputStream я закрыл dos и думал, что все прошло хорошо.
Однако, внутри dos пытался вызвать flush на bos, что вызвало ошибку "Disk Full". Это исключение было перехвачено DataOutputStream, и в результате bos не был закрыт, а значит, fos оставался открытым.
На более позднем этапе файл был переименован с (чем-то с .tmp) в его реальное имя. В результате система отслеживания дескрипторов файлов в Java потеряла из виду оригинальный .tmp, хотя он все еще оставался открытым!
Чтобы решить эту проблему, мне пришлось сначала вручную вызвать flush на DataOutputStream, поймать IOException и затем закрыть FileOutputStream самостоятельно.
Надеюсь, это поможет кому-то.
Если вы видите это в автоматизированных тестах, лучше всего корректно закрывать все файлы между запусками тестов.
Если вы не уверены, какие файлы остались открытыми, хорошим местом для начала будет просмотр вызовов open, которые вызывают исключения! 😄
Если у вас есть дескриптор файла, который должен оставаться открытым ровно столько, сколько жив родительский объект, вы можете добавить метод finalize к родительскому объекту, который будет закрывать дескриптор файла. Также вызывайте System.gc() между тестами.
Как создать строку Java из содержимого файла?
Как прочитать большой текстовый файл построчно с помощью Java?
Чтение текстового файла в Java
Как сохранить строку в текстовый файл с помощью Java?
"Java Files.write вызывает NoSuchFileException: как решить проблему?"