Рубрики
Без рубрики

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

Узнайте, когда и как избежать исключения “Слишком много открытых файлов” в Java.

Автор оригинала: baeldung.

1. введение

Распространенной ловушкой при работе с файлами в Java является возможность исчерпания доступных файловых дескрипторов.

В этом уроке мы рассмотрим эту ситуацию и предложим два способа избежать этой проблемы.

2. Как JVM обрабатывает файлы

Хотя JVM отлично изолирует нас от операционной системы, он делегирует низкоуровневые операции, такие как управление файлами, операционной системе.

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

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

3. Утечка Файловых Дескрипторов

Напомним, что для каждой ссылки на файл в нашем Java-приложении у нас есть соответствующий файловый дескриптор в ОС. Этот дескриптор будет закрыт только после удаления экземпляра ссылки на файл. Это произойдет на этапе сбора мусора .

Однако, если ссылка остается активной и открывается все больше и больше файлов, то в конечном итоге в ОС закончатся файловые дескрипторы для выделения. В этот момент он перенаправит эту ситуацию в JVM, что приведет к появлению IOException .

Мы можем воспроизвести эту ситуацию с помощью короткого модульного теста:

@Test
public void whenNotClosingResoures_thenIOExceptionShouldBeThrown() {
    try {
        for (int x = 0; x < 1000000; x++) {
            FileInputStream leakyHandle = new FileInputStream(tempFile);
        }
        fail("Method Should Have Failed");
    } catch (IOException e) {
        assertTrue(e.getMessage().containsIgnoreCase("too many open files"));
    } catch (Exception e) {
        fail("Unexpected exception");
    }
}

В большинстве операционных систем процесс JVM исчерпает файловые дескрипторы до завершения цикла, тем самым вызывая исключение IOException .

Давайте посмотрим, как мы можем избежать этого состояния при правильной обработке ресурсов.

4. Управление ресурсами

Как мы уже говорили, файловые дескрипторы освобождаются процессом JVM во время сборки мусора .

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

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

4.1. Ручное Освобождение Ссылок

Ручное освобождение ссылок было обычным способом обеспечить надлежащее управление ресурсами до JDK 8.

Мы не только должны явно закрыть любой файл, который мы открываем , но и убедиться, что мы делаем это, даже если наш код терпит неудачу и создает исключения. Это означает использование ключевого слова finally :

@Test
public void whenClosingResoures_thenIOExceptionShouldNotBeThrown() {
    try {
        for (int x = 0; x < 1000000; x++) {
            FileInputStream nonLeakyHandle = null;
            try {
                nonLeakyHandle = new FileInputStream(tempFile);
            } finally {
                if (nonLeakyHandle != null) {
                    nonLeakyHandle.close();
                }
            }
        }
    } catch (IOException e) {
        assertFalse(e.getMessage().toLowerCase().contains("too many open files"));
        fail("Method Should Not Have Failed");
    } catch (Exception e) {
        fail("Unexpected exception");
    }
}

Поскольку блок finally всегда выполняется, это дает нам возможность правильно закрыть нашу ссылку, тем самым ограничивая количество открытых дескрипторов.

4.2. Использование try-with-resources

JDK 7 предлагает нам более чистый способ утилизации ресурсов. Он обычно известен как try-with-resources и позволяет нам делегировать распоряжение ресурсами, включив ресурс в определение try :

@Test
public void whenUsingTryWithResoures_thenIOExceptionShouldNotBeThrown() {
    try {
        for (int x = 0; x < 1000000; x++) {
            try (FileInputStream nonLeakyHandle = new FileInputStream(tempFile)) {
                // do something with the file
            }
        }
    } catch (IOException e) {
        assertFalse(e.getMessage().toLowerCase().contains("too many open files"));
        fail("Method Should Not Have Failed");
    } catch (Exception e) {
        fail("Unexpected exception");
    }
}

Здесь мы объявили не протекающий дескриптор внутри оператора try . Из-за этого Java закроет ресурс для нас вместо того, чтобы нам нужно было использовать наконец.

5. Заключение

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

Полный исходный код статьи доступен по адресу на GitHub .