0

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

18

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

5 ответ(ов)

0

На 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 пытается создать "канальные" дескрипторы файлов, которые будут подключены к стандартному потоку ввода/вывода/ошибок внешнего приложения.

0

Для UNIX:

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

Сначала проверьте текущую емкость дескрипторов файлов:

   $ ulimit -n

Затем измените лимит в соответствии с вашими требованиями:

   $ ulimit -n <значение>

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

0

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

0

Хотя в большинстве случаев ошибка довольно очевидна, так как файловые дескрипторы не были закрыты, я столкнулся с ситуацией в 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 самостоятельно.

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

0

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

Если вы не уверены, какие файлы остались открытыми, хорошим местом для начала будет просмотр вызовов open, которые вызывают исключения! 😄

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь