Почему необходимо закрывать соединения с файлами в Java?

Я прочитал, что этот файл является неуправляемым ресурсом, и сборщик мусора о нем не позаботится.

Если вы не закроете файл, я уверен, что ссылка на файл будет собирать мусор, если на него ничего не ссылается. Так что же остается открытым? Это что-то на уровне операционной системы? Как и для соединений SQL, я знаю, что ОС поддерживает открытый порт TCP, и у вас могут закончиться порты. Но что остается открытым в случае файла?

2 ответа

Сборщик мусора может в конечном итоге освободить ресурсы ОС, благодаря finalize() метод в классах, которые обертывают ресурсы. Однако высвобождение ресурсов ОС в какой-то момент в будущем недостаточно.

В частности, есть две проблемы:

  1. Вы можете достичь предела ОС задолго до того, как GC сможет запустить. Достижение такого предела не запускает автоматический сборщик мусора, как при исчерпании пространства кучи.
  2. Даже если вы освободите ресурс ОС, у вас могут остаться буферы уровня приложения, которые не будут очищены.

Например, Debian Linux имеет ограничение по умолчанию на количество открытых файлов 1024, чтобы предотвратить неправильную работу самой программы. Рассмотрим эту программу, которая оптимально должна использовать только один FD на одну итерацию:

import java.io.*;
class Foo {
  public static void main(String[] args) throws Exception {
    for(int i=0; i<2000; i++) {
      FileInputStream fis = new FileInputStream("Foo.java");
    }
  }
}

Вот что происходит, когда вы запускаете его:

$ java Foo
Exception in thread "main" java.io.FileNotFoundException: Foo.java (Too many open files)
        at java.io.FileInputStream.open0(Native Method)
        at java.io.FileInputStream.open(FileInputStream.java:195)
        at java.io.FileInputStream.<init>(FileInputStream.java:138)
        at java.io.FileInputStream.<init>(FileInputStream.java:93)
        at Foo.main(Foo.java:5)

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

Вот еще один пример программы, которая записывает строку в файл, а затем читает ее обратно:

import java.io.*;
class Foo {
  static void writeConfig(String s) throws IOException {
    BufferedWriter fw = new BufferedWriter(new FileWriter("config.txt"));
    fw.write(s);
    System.out.println("Successfully wrote config");
  }
  static String readConfig() throws IOException {
    BufferedReader reader = new BufferedReader(new FileReader("config.txt"));
    return reader.readLine();
  }
  public static void main(String[] args) throws Exception {
    writeConfig("Hello World");
    System.gc();  // Futile attempt to rely on the GC
    String input = readConfig();
    System.out.println("The config string is: " + input);
  }
}

Вот что вы получаете:

$ java Foo
Successfully wrote config
The config string is: null

Записанная строка не попала в файл. Если вы закрыли BufferedWriter, это не будет проблемой.

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

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

Другие вопросы по тегам