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: как решить проблему?"