Java OutOfMemoryError при чтении большого текстового файла
Я новичок в Java и работаю над чтением очень больших файлов, нуждаюсь в помощи, чтобы понять проблему и решить ее. У нас есть код устаревшего кода, который нужно оптимизировать, чтобы он работал правильно. Размер файла может варьироваться от 10 до 10 гб. только проблема начинается, когда файл начинается за пределами размера 800 МБ.
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
byte[] localbuffer = new byte[2048];
ByteArrayOutputStream bArrStream = new ByteArrayOutputStream();
int i = 0;
while (-1 != (i = inFileReader.read(buffer))) {
bArrStream.write(localbuffer, 0, i);
}
byte[] data = bArrStream.toByteArray();
inFileReader.close();
bos.close();
Мы получаем ошибку
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2271)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140)
Любая помощь будет оценена?
Ответы
Ответ 1
Попробуйте использовать java.nio.MappedByteBuffer.
http://docs.oracle.com/javase/7/docs/api/java/nio/MappedByteBuffer.html
Вы можете сопоставить содержимое файла на память, не копируя его вручную. Высокоуровневые операционные системы предлагают сопоставление памяти, а Java имеет API для использования этой функции.
Если мое понимание верное, отображение памяти не загружает весь файл целиком в память (что означает "загружается и выгружается частично по мере необходимости" ), поэтому я думаю, что 10-гигабайтный файл не будет уничтожать вашу память.
Ответ 2
Несмотря на то, что вы можете увеличить предел памяти JVM, это бесполезно и выделяет огромную память, такую как 10 ГБ, для обработки переполнения файлов и ресурса.
В настоящее время вы используете "ByteArrayOutputStream", который хранит внутреннюю память для хранения данных. Эта строка в вашем коде добавляет последний прочитанный фрагмент файла 2KB в конец этого буфера:
bArrStream.write(localbuffer, 0, i);
bArrStream продолжает расти, и в итоге у вас заканчивается память.
Вместо этого вы должны реорганизовать свой алгоритм и обработать файл потоковым способом:
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
byte[] localbuffer = new byte[2048];
int i = 0;
while (-1 != (i = inFileReader.read(buffer))) {
//Deal with the current read 2KB file chunk here
}
inFileReader.close();
Ответ 3
Виртуальная машина Java (JVM) работает с фиксированным верхним пределом памяти, который вы можете изменить таким образом:
java -Xmx1024m ....
например. вышеприведенная опция (-Xmx...) устанавливает ограничение на 1024 мегабайта. Вы можете внести поправку по мере необходимости (в пределах вашей машины, ОС и т.д.). Обратите внимание, что это отличается от традиционных приложений, которые будут выделять все больше и больше памяти из ОС по требованию.
Однако лучшим решением является переработка вашего приложения, так что вам не нужно загружать весь файл в память за один раз. Таким образом, вам не нужно настраивать JVM, и вы не накладываете огромный объем памяти.
Ответ 4
Запустите Java с параметром командной строки -Xmx, который устанавливает максимальный размер кучи.
Подробнее см. здесь..
Ответ 5
Вы не можете прочитать текстовый файл 10GB в памяти. Сначала вы должны прочитать X МБ, сделать с ним что-то, а затем прочитать следующий X МБ.
Ответ 6
Попытка использования большого размера чтения буфера может составлять 10 мб, а затем проверить.
Ответ 7
Проблема заключается в том, что вы делаете. Чтение целых файлов в память всегда и везде - плохая идея. Вы действительно не сможете читать 10GB файл в памяти с использованием современных технологий, если у вас нет довольно поразительного оборудования. Найдите способ их обработки по строкам, запись по записи, фрагмент с помощью куска,...
Ответ 8
Обязательно ли получить полный ByteArray()
выходного потока?
byte[] data = bArrStream.toByteArray();
Лучший подход читается по строкам и записывается по строкам. Вы можете использовать BufferedReader
или Scanner
для чтения больших файлов, как показано ниже.
import java.io.*;
import java.util.*;
public class FileReadExample {
public static void main(String args[]) throws FileNotFoundException {
File fileObj = new File(args[0]);
long t1 = System.currentTimeMillis();
try {
// BufferedReader object for reading the file
BufferedReader br = new BufferedReader(new FileReader(fileObj));
// Reading each line of file using BufferedReader class
String str;
while ( (str = br.readLine()) != null) {
System.out.println(str);
}
}catch(Exception err){
err.printStackTrace();
}
long t2 = System.currentTimeMillis();
System.out.println("Time taken for BufferedReader:"+(t2-t1));
t1 = System.currentTimeMillis();
try (
// Scanner object for reading the file
Scanner scnr = new Scanner(fileObj);) {
// Reading each line of file using Scanner class
while (scnr.hasNextLine()) {
String strLine = scnr.nextLine();
// print data on console
System.out.println(strLine);
}
}
t2 = System.currentTimeMillis();
System.out.println("Time taken for scanner:"+(t2-t1));
}
}
Вы можете заменить System.out
на ByteArrayOutputStream
в приведенном выше примере.
Пожалуйста, посмотрите ниже статью для более подробной информации: Прочитать большой файл
Посмотрите на связанный вопрос SE:
Сканер vs. BufferedReader
Ответ 9
ByteArrayOutputStream
записывается в буфер памяти. Если это действительно так, как вы хотите, чтобы он работал, тогда вам нужно определить размер кучи JVM после максимально возможного размера ввода. Кроме того, если возможно, вы можете проверить размер ввода до начала обработки, чтобы сэкономить время и ресурсы.
Альтернативный подход - это потоковое решение, в котором известно количество памяти, используемой во время выполнения (возможно, настраивается, но все еще известно до запуска программы), но если это возможно или полностью зависит от вашего домена приложения (потому что вы можете " t использовать буфер в памяти больше) и, возможно, архитектуру остальной части вашего кода, если вы не можете/не хотите ее изменять.
Ответ 10
Привет, я предполагаю, что вы читаете большой файл txt, и данные устанавливаются по строкам, используйте метод подсчета строк за строкой. Как я знаю, вы можете читать до 6 ГБ, может быть больше. Я настоятельно рекомендую вам попробовать этот подход.
DATA1
DATA2
...
// Open the file
FileInputStream fstream = new FileInputStream("textfile.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fstream));
String strLine;
//Read File Line By Line
while ((strLine = br.readLine()) != null) {
// Print the content on the console
System.out.println (strLine);
}
//Close the input stream
br.close();
Отказ от фрагмента кода
Ответ 11
Прочитайте файл итеративно linewise. Это значительно снизит потребление памяти. В качестве альтернативы вы можете использовать
FileUtils.lineIterator(theFile, "UTF-8" );
предоставляется Apache Commons IO.
FileInputStream inputStream = null;
Scanner sc = null;
try {
inputStream = new FileInputStream(path);
sc = new Scanner(inputStream, "UTF-8");
while (sc.hasNextLine()) {
String line = sc.nextLine();
// System.out.println(line);
}
// note that Scanner suppresses exceptions
if (sc.ioException() != null) {
throw sc.ioException();
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (sc != null) {
sc.close();
}
}
Ответ 12
Вы должны увеличить размер кучи, как указано в следующем ответе:
Увеличить размер кучи в Java
Но помните, что время выполнения Java и ваш код занимают некоторое пространство, поэтому добавьте некоторый буфер в желаемый максимум.
Ответ 13
Короткий ответ,
не делая ничего, вы можете увеличить текущий предел в 1,5 раза. Это означает, что если вы можете обрабатывать 800 МБ, вы можете обрабатывать 1200 МБ. Это также означает, что если какой-то трюк с java -Xm ....
вы можете перейти к точке, где ваш текущий код может обрабатывать 7 ГБ, ваша проблема решена, потому что фактор 1.5 приведет вас к 10,5 ГБ, если у вас есть это пространство на вашем системы и что JVM может получить его.
Длинный ответ:
Ошибка довольно самоописательна. Вы нажимаете ограничение на практическую память в своей конфигурации. Существует много размышлений о пределе, который вы можете иметь с JVM, я недостаточно знаю об этом, так как я не могу найти никакой официальной информации. Однако вы каким-то образом ограничены ограничениями, такими как доступный обмен, использование адресного пространства ядра, фрагментация памяти и т.д.
Теперь происходит то, что объекты ByteArrayOutputStream
создаются с использованием буфера по умолчанию размером 32, если вы не поставляете какой-либо размер (это ваш случай). Всякий раз, когда вы вызываете метод write
на объект, начинается встроенный механизм. openjdk реализация релиза 7u40-b43, которая, кажется, идеально сочетается с выходом вашей ошибки, использует внутренний метод ensureCapacity
, чтобы проверить, что буфер достаточно места для размещения байтов, которые вы хотите записать. Если места недостаточно, для увеличения размера буфера вызывается другой внутренний метод grow
. Метод grow
определяет соответствующий размер и вызывает метод copyOf
из класса Arrays
для выполнения задания.
Соответствующий размер буфера является максимальным между текущим размером и размером, необходимым для хранения всего содержимого (настоящего содержимого и нового содержимого для записи).
Метод copyOf
из класса Arrays
(следовать по ссылке) выделяет пространство для нового буфера, копирует содержимое старого буфера к новому и верните его на grow
.
Ваша проблема возникает при распределении пространства для нового буфера. После некоторого write
вы попали в точку, в которой исчерпана доступная память: java.lang.OutOfMemoryError: Java heap space
.
Если мы рассмотрим детали, вы читаете куски 2048. Итак
- ваш первый напишет, чтобы увеличить размер буфера от 32 до 2048
- ваш второй вызов удвоит его до 2 * 2048
- ваш третий вызов займет 2 ^ 2 * 2048, вы должны время написать еще два раза перед необходимостью выделения.
- тогда 2 ^ 3 * 2048, у вас будет время для 4 комментариев, прежде чем выделять снова.
- в какой-то момент ваш буфер будет иметь размер 2 ^ 18 * 2048, который составляет 2 ^ 19 * 1024 или 2 ^ 9 * 2 ^ 20 (512 МБ).
- затем 2 ^ 19 * 2048, который составляет 1024 МБ или 1 ГБ.
Что-то, что неясно в вашем описании, это то, что вы можете как-то читать до 800 МБ, но не можете выйти за его пределы. Вы должны объяснить это мне.
Я ожидаю, что ваш предел будет ровно силой 2 (или близко, если мы будем использовать мощность 10 единиц). В этой связи я ожидаю, что вы сразу начнете испытывать проблемы над одним из них: 256 МБ, 512 МБ, 1 ГБ, 2 ГБ и т.д.
Когда вы нажимаете этот предел, это не означает, что вы потеряли память, это просто означает, что нельзя выделить другой буфер, вдвое превышающий размер уже имеющегося буфера. Это наблюдение открывает возможности для улучшения вашей работы: найдите максимальный размер буфера, который вы можете выделить, и зарезервируйте его заранее, вызвав соответствующий конструктор
ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(myMaxSize);
Преимущество состоит в том, что сокращение распределения памяти фоновой памяти происходит под капотом, чтобы вы были счастливы. Делая это, вы сможете перейти к 1.5 лимиту, который у вас есть прямо сейчас. Это просто потому, что в последний раз, когда буфер был увеличен, он переместился с половины текущего размера на текущий размер, и в какой-то момент у вас в памяти был как текущий буфер, так и старый. Но вы не сможете выйти за пределы 3-х кратного предела, который у вас сейчас есть. Объяснение точно такое же.
Было сказано, что у меня нет никакого волшебного предложения, чтобы решить проблему, кроме обработки ваших данных кусками заданного размера, по одному куску за раз. Другим хорошим подходом будет использование предложения Такахико Кавасаки и использование MappedByteBuffer
. Имейте в виду, что в любом случае вам понадобится не менее 10 ГБ физической памяти или swap-памяти, чтобы иметь возможность загружать файл размером 10 ГБ.
см
Ответ 14
Подумав об этом, я решил поставить второй ответ. Я рассмотрел преимущества и недостатки ответа на этот второй ответ, и преимущества этого стоит. Итак, вот оно.
Большинство предлагаемых соображений забывают об одном факте: существует встроенный предел в размере массивов (включая ByteArrayOutputStream
), которые вы можете иметь в Java. И этот предел диктуется самым большим значением int
, которое составляет 2 ^ 31 - 1 (немного меньше 2Giga). Это означает, что вы можете читать максимум 2 ГБ (-1 байт) и помещать его в один ByteArrayOutputStream
. Предел может быть меньше размера массива, если VM хочет большего контроля.
Мое предложение - использовать ArrayList
of byte[]
вместо одного byte[]
, содержащего полный контент файла. А также удалите ненужный шаг ввода ByteArrayOutputStream
перед тем, как поместить его в окончательный массив data
. Вот пример, основанный на вашем исходном коде:
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
// good habits are good, define a buffer size
final int BUF_SIZE = (int)(Math.pow(2,30)); //1GB, let not go close to the limit
byte[] localbuffer = new byte[BUF_SIZE];
int i = 0;
while (-1 != (i = inFileReader.read(localbuffer))) {
if(i<BUF_SIZE){
data.add( Arrays.copyOf(localbuffer, i) )
// No need to reallocate the reading buffer, we copied the data
}else{
data.add(localbuffer)
// reallocate the reading buffer
localbuffer = new byte[BUF_SIZE]
}
}
inFileReader.close();
// Process your data, keep in mind that you have a list of buffers.
// So you need to loop over the list
Просто запуск вашей программы должен работать нормально на 64-битной системе с достаточной физической памятью или свопом. Теперь, если вы хотите ускорить его, чтобы правильно настроить размер виртуальной машины, куча в начале, запустите с параметрами -Xms
и -Xmx
. Например, если вы хотите, чтобы куча 12 ГБ могла обрабатывать файл 10 ГБ, используйте java -Xms12288m -Xmx12288m YourApp