О слишком большом количестве открытых файлов с помощью java?

При просмотре кода коллеги, найденного ниже кода

    BufferedReader br = new BufferedReader(new FileReader(PATH + fileName));
    //...

просто прочитайте файл и объедините эти строки в одну строку, но я не нашел близкого кода, поэтому я думаю, что это должно вызвать утечку ресурсов и, наконец, вызвать too many open files error, поэтому, чтобы доказать это, я пишу тест

for (int i = 0; i < 7168; i++) { // ulimit -n ==> 7168
    BufferedReader br = new BufferedReader(new FileReader("src/main/resources/privateKey/foo.pem"));
    System.out.println(br.readLine());
}
System.in.read();

Очень странно, все ок, ожидаемое исключение не выдает.

И проверьте реально открытые файлы в командной строке

➜  ~ lsof -p 16276 | grep 'foo.pem' | wc -l
    2538

почему только 2538, а не 7168?

Так что же не так? как вызвать too many open files error ?


Как предложил @GhostCat, измените 7168 --> Integer.MAX_VALUE, на этот раз это вызвало

java.io.FileNotFoundException: src/main/resources/privateKey/foo.pem (Too many open files in system)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)

когда я 27436, и в этом случае проверка реальных открытых файлов в командной строке

➜  ~ lsof | grep foo.pem | wc -l
    7275

а где остались файлы (27346 - 7275)? а почему ulimit номер не работает?


person zhuguowei    schedule 13.04.2017    source источник
comment
Я бы начал такой эксперимент с цикла while(true)... и, может быть, распечатать этот счетчик в каждый файл... а также проверить, что происходит на диске.   -  person GhostCat    schedule 13.04.2017
comment
Спасибо! пожалуйста, смотрите мою дополнительную информацию   -  person zhuguowei    schedule 13.04.2017
comment
И помимо этого: вы, вероятно, упустили из виду мое предложение: я бы попробовал открывать разные файлы для записи, а также записывать в каждый файл разное содержимое. Если мой ответ не является полностью фиктивным, меня не удивит, если вы обнаружите, что ваша ОС каким-то образом способна оптимизировать множество запросов на чтение одного и того же файла. Но запись разных вещей в разные файлы — это то, что нельзя так просто оптимизировать. Подскажите, что из этого выйдет ;-)   -  person GhostCat    schedule 13.04.2017


Ответы (3)


Я предполагаю, что работает сборщик мусора, который находит множество недоступных BufferedReader объектов и собирает их. Это приводит к финализации базовых объектов потока... что закрывает их.

Чтобы сломать этот код, добавьте объекты BufferedReader в список, чтобы они оставались доступными.


И вот почему я думаю, что замена 7168 на MAXINT работает.

Когда JVM запускается, она будет использовать относительно небольшую кучу. Одна из вещей, которые происходят во время сборки мусора, заключается в том, что JVM решает, нужно ли ей изменять размер кучи. Итак, вот что могло бы произойти:

  • JVM начинается с кучи, которая слишком мала для хранения 7168 открытых файлов + объектов BufferedReader. (Помните, что каждый из последних, вероятно, имеет заранее выделенный буфер!)

  • Вы начинаете открывать файлы.

  • Примерно на N=7168 - 2538 куча заполняется всеми объектами BufferedReader + объектами FileInputStream + разным детритом от запуска/прогрева JVM.

  • GC запускается и заставляет (вероятно) все объекты BufferedReader собираться/завершаться/закрываться.

  • Затем GC решает, что ему нужно расширить кучу. Теперь у вас достаточно места в куче для большего количества открытых объектов BufferedReader, чем позволяет ваш ulimit.

  • Вы возобновляете открытие файлов... а затем достигаете предела количества открытых файлов.

Это один из возможных шаблонов.


Если вы действительно хотите исследовать это, я советую вам включить ведение журнала GC и посмотреть, сможете ли вы сопоставить количество FD, о которых сообщает lsof, с запусками GC.

(Вы можете попробовать добавить вызовы sleep между каждым открытием, чтобы упростить получение измерений lsof. Но это может изменить поведение JVM другими способами...)

person Stephen C    schedule 13.04.2017
comment
Я так не думаю, потому что каждый раз выполнение команды lsof приводило к одному и тому же результату. Если бы участвовал gc, то результат изменился бы, то есть значение стало бы меньше до 0 - person zhuguowei; 13.04.2017
comment
Я бы согласился со Стивеном. Но почему бы вам не протестировать это: переопределить BufferedReader, переопределить финализацию, посмотреть, что произойдет...? - person GPI; 13.04.2017
comment
@zhuguowei - Вы предполагаете, что GC работает все время. На самом деле он запускается только тогда, когда это необходимо; например когда пространство заполняется. Таким образом, поведение, которое вы предлагаете, не произойдет. - person Stephen C; 14.04.2017

У меня нет точного объяснения, но есть некоторые дополнительные мысли: «мы» должны понимать, что все не так просто, как кажется на первый взгляд.

Дело в том, что в игру вступают несколько уровней абстракции. Есть JVM и JIT; а затем есть операционная система ниже тех.

Значение: учитывая эти абстракции, просто слишком наивно ожидать, что каждый новый BufferReader напрямую ведет к другому дескриптору файла. Меня не удивит, если здесь включится ядро ​​Linux; и просто «говорит» JVM, что «да, я открыл этот файл и прочитал его для вас, вот его содержимое». Но в «реальности» ядро ​​Linux понимает, что этот файл не трогали и не меняли с момента последнего запроса на чтение…

person GhostCat    schedule 13.04.2017

  1. JVM неявно обновляет значение ulimit

    String [] cmdArray = {"sh","-c","ulimit -n"};
    Process p = Runtime.getRuntime().exec(cmdArray);
    BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
    System.out.println(in.readLine()); //it is 10240 not 7168
    
  2. @Stephen C прав, участвует GC.

Я создаю MyBufferedReader extends BufferedRead и переопределяю метод finalize

@Override
protected void finalize() throws Throwable {
    System.out.printf("Thread: %s finalize it and total: %d %n",Thread.currentThread().getName(),count.getAndAdd(1));
}

Получил ниже информацию

Thread: Finalizer finalize it and total: 9410 

и в командной строке

➜  ~ lsof -p 5309 | grep 'taicredit_private_key_pkcs8' | wc -l
     830

и 9410 + 830 = 10240

person zhuguowei    schedule 16.04.2017