Моя программа Java, которая читает большой текстовый файл, исчерпала память, может ли кто-нибудь объяснить, почему?
У меня есть большой текстовый файл с 20 миллионами строк текста. Когда я читаю файл, используя следующую программу, он работает нормально, и на самом деле я могу читать гораздо большие файлы без проблем с памятью.
public static void main(String[] args) throws IOException {
File tempFile = new File("temp.dat");
String tempLine = null;
BufferedReader br = null;
int lineCount = 0;
try {
br = new BufferedReader(new FileReader(tempFile));
while ((tempLine = br.readLine()) != null) {
lineCount += 1;
}
} catch (Exception e) {
System.out.println("br error: " +e.getMessage());
} finally {
br.close();
System.out.println(lineCount + " lines read from file");
}
}
Однако, если мне нужно добавить некоторые записи в этот файл перед его чтением, BufferedReader потребляет огромное количество памяти (я только что использовал диспетчер задач Windows, чтобы контролировать это, а не очень научное, но это демонстрирует проблему). Измененная программа приведена ниже, которая совпадает с первой, за исключением того, что я сначала добавляю одну запись в файл.
public static void main(String[] args) throws IOException {
File tempFile = new File("temp.dat");
PrintWriter pw = null;
try {
pw = new PrintWriter(new BufferedWriter(new FileWriter(tempFile, true)));
pw.println(" ");
} catch (Exception e) {
System.out.println("pw error: " + e.getMessage());
} finally {
pw.close();
}
String tempLine = null;
BufferedReader br = null;
int lineCount = 0;
try {
br = new BufferedReader(new FileReader(tempFile));
while ((tempLine = br.readLine()) != null) {
lineCount += 1;
}
} catch (Exception e) {
System.out.println("br error: " +e.getMessage());
} finally {
br.close();
System.out.println(lineCount + " lines read from file");
}
}
Снимок экрана диспетчера задач Windows, где большой удар в строке показывает потребление памяти при запуске второй версии программы.
![task manager screenshot]()
Итак, я смог прочитать этот файл, не исчерпав память. Но у меня гораздо больше файлов с более чем 50 миллионами записей, которые сталкиваются с избытком памяти, когда я запускаю эту программу против них? Может кто-нибудь объяснить, почему первая версия программы отлично работает на файлы любого размера, но вторая программа ведет себя по-разному и заканчивается неудачей? Я запускаю Windows 7 с помощью:
java-версия "1.7.0_05"
Java (TM) SE Runtime Environment (сборка 1.7.0_05-b05)
Клиентская виртуальная машина Java HotSpot (TM) (сборка 23.1-b03, смешанный режим, совместное использование)
Ответы
Ответ 1
вы можете запустить Java-VM с VM-Options
-XX:+HeapDumpOnOutOfMemoryError
это напишет кучу дампа в файл, который можно проанализировать для обнаружения подозреваемых в утечке
Используйте "+", чтобы добавить параметр и "-", чтобы удалить параметр.
Если вы используете Eclipse плагин для анализатора памяти Java MAT, чтобы получить кучи-дампы от запуска виртуальных машин с некоторыми хорошими анализами для подозреваемых в утечке и др.
Ответ 2
Предполагалось, что цикл очень плотный (быстрый), а tempLine вне цикла с более длинным кодом создает непрерывно новые объекты без сбора мусора, получая раннюю возможность.
Попробуйте следующее:
for (;;) {
String tempLine = br.readLine();
if (tempLine == null) {
break;
}
++lineCount;
}
Ответ 3
Каждый раз, когда вы выполняете java после Java-процедуры, вы создаете совершенно новый объект:
tempLine = br.readLine()
Я считаю, что каждый раз, когда вы вызываете readLine(), возможно, создается новый объект String, который остается в куче каждый раз, когда вызывается переназначение, чтобы назначить значение tempLine.
Поэтому, поскольку GC не постоянно называется, тысячи объектов могут оставаться в куче в течение нескольких секунд.
Некоторые люди говорят о своей плохой идее вызывать System.gc() каждые 1000 строк или около того, но мне было бы любопытно, исправляет ли это вашу проблему. Кроме того, вы можете запустить эту команду после каждой строки, чтобы пометить каждый объект как сборщик мусора:
tempLine=null;
Ответ 4
pw = new PrintWriter(new BufferedWriter(new FileWriter(tempFile, true)));
Вы пытались не использовать BufferedWriter? Если вы добавляете несколько строк до конца, возможно, вам не нужен буфер? Если вы это сделаете, подумайте об использовании массива байтов (коллекции или строковый построитель). Наконец, вы попробовали то же самое в java 1.6_32? Может быть ошибкой в новой версии одного из авторов.
Вы можете распечатать свободную память после до и после pw.close();
System.out.println("before wr close :" + Runtime.getRuntime().freeMemory());
и аналогичный после закрытия и после закрытия читателя
Ответ 5
Это может быть связано с тем, что у вас может не быть возврата строки/каретки в вашем файле вообще. В этом случае readLine()
пытается создать только одну строку из вашего файла, которая, вероятно, заканчивается из mememory.
Java-документ readLine():
Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.
Ответ 6
Вы пробовали:
A) создание нового экземпляра файла для чтения, но указание на тот же файл.
а также
B) чтение совершенно другого файла во второй части.
Мне интересно, есть ли объект File по-прежнему привязан к PrintWriter или если ОС делает что-то смешное с файлами. Те тесты должны показать вам, где сосредоточиться.
Это не похоже на проблему с кодом, и ваша логика для размышлений о том, что она не должна прерываться, кажется звуковой, поэтому она должна быть базовой базой.
Ответ 7
вам нужно запустить java с большей кучей. Попробуйте -Xmx1024m как параметр в команде java.
В основном вам понадобится больше памяти, чем размер файла.