2013-08-29 9 views
15

Я новичок в Java и работаю над чтением очень больших файлов, нуждаюсь в помощи, чтобы понять проблему и решить ее. У нас есть код устаревшего кода, который нужно оптимизировать, чтобы он работал правильно. Размер файла может варьироваться от 10 до 10 гб. только проблема начинается, когда файл начинается за пределами размера 800 МБ.Java OutOfMemoryError при чтении большого текстового файла

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

В примере кода, который вы упомянули, вы просто загружаете весь файл в 'ByteArrayOutputStream'. Что такое прецедент? Действительно нужны все данные файла в 'byte []'? – Santosh

+0

Можете ли вы сообщить мне, какую версию JDK вы планируете использовать, у меня есть другое решение для JDK 8 и JDK7 или меньше. – Bhupi

+0

@ Luffy имеет смысл ответить на этот вопрос, не зная ** почему ** столько данных считывается в память? – k3b

ответ

7

Виртуальная машина Java (JVM) работает с ограничением фиксированной верхней памяти, которые вы можете изменить таким образом:

java -Xmx1024m .... 

например вышеприведенная опция (-Xmx ...) устанавливает ограничение на 1024 мегабайта. Вы можете внести поправку по мере необходимости (в пределах вашей машины, ОС и т. Д.). Обратите внимание, что это отличается от традиционных приложений, которые будут выделять все больше и больше памяти из ОС по требованию.

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

4

Вы не можете прочитать текстовый файл 10GB в памяти. Сначала вы должны прочитать X МБ, сделать с ним что-то, а затем прочитать следующий X МБ.

+3

Если у него 10Gb и 64-битная JVM, он * может * сделать это. Он, вероятно, не должен был. –

+0

любая помощь относительно чтения в разделах? –

+0

@Brian Нет, он не может. Даже до 64 бит существует только ограниченный размер элементов в массиве. – sigi

3

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

Альтернативный подход - это потоковое решение, в котором известно количество памяти, используемой во время выполнения (возможно, настраивается, но все еще известно до запуска программы), но если это возможно или полностью зависит от домена вашего приложения (потому что вы можете 't использовать буфер в памяти больше) и, возможно, архитектуру остальной части вашего кода, если вы не можете/не хотите ее изменять.

4

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

4

Проблема заключается в том, что вы делаете. Чтение целых файлов в память всегда и везде - плохая идея. Вы действительно не сможете читать 10GB-файл в памяти с использованием современных технологий, если у вас нет довольно поразительного оборудования. Найти способ обработки их по строкам, запись по записи, кусок по куску, ...

+0

«Чтение целых файлов в память всегда и везде плохая идея»? Скажи это моему редактору! :-) –

17

Попробуйте использовать java.nio.MappedByteBuffer.

http://docs.oracle.com/javase/7/docs/api/java/nio/MappedByteBuffer.html

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

Если мое понимание верное, отображение памяти не загружает весь контент файла в память (что означает «загружено и выгружено частично по мере необходимости»), поэтому я думаю, что 10 ГБ-файл не будет уничтожать вашу память.

4

Обязательно ли получать весь выходной поток 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 в приведенном выше примере.

Пожалуйста, обратите внимание на статьи ниже для более подробной информации: Read Large File

взглянуть на соответствующий SE вопрос:

Scanner vs. BufferedReader

11

Даже если вы можете увеличить объем памяти виртуальной машины Java, это само собой и выделяя огромную память, такую ​​как 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

Здравствуйте, я предполагаю, что вы читаете большой текстовый файл и данные устанавливаются построчно, используйте построчно подход к чтению. Как я знаю, вы можете читать до 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(); 

Refrence for the code fragment

3

Прочитайте файл итеративно построчной. Это значительно снизит потребление памяти. В качестве альтернативы вы можете использовать

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(); 
} 

}

5

Run Java с параметром командной строки -Xmx, который устанавливает максимальный размер кучи.

See here for details..

+0

Эта ссылка не работает для меня, не могли бы вы поместить важную информацию здесь в дополнение к ссылке? – innoSPG

2

Вы должны увеличить размер кучи, как указано в следующем ответе:

Increase heap size in Java

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

2

Короткий ответ,

, ничего не делая, вы можете нажать на текущий предел в 1,5 раза. Это означает, что если вы можете обрабатывать 800 МБ, вы можете обрабатывать 1200 МБ. Это также означает, что если какой-либо трюк с java -Xm .... вы можете перейти к точке, где ваш текущий код может обрабатывать 7 ГБ, ваша проблема будет решена, потому что фактор 1.5 приведет вас к 10,5 ГБ, если у вас есть это пространство в вашей системе и что JVM может это получить.

Длинный ответ:

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

Что происходит сейчас, так что объекты ByteArrayOutputStream создаются с использованием буфера по умолчанию размером 32, если вы это делаете не поставляйте никаких размеров (это ваш случай). Всякий раз, когда вы вызываете метод write на объекте, запускается внутренняя машина. openjdk implementation release 7u40-b43, который, кажется, идеально подходит для вывода вашей ошибки, использует внутренний метод ensureCapacity, чтобы проверить, что в буфере достаточно места для размещения байтов, которые вы хотите записать. Если места недостаточно, для увеличения размера буфера вызывается другой внутренний метод grow. Метод grow определяет соответствующий размер и вызывает метод copyOf из класса Arrays для выполнения задания. Соответствующий размер буфера - это максимальный размер между текущим размером и размером, необходимым для хранения всего содержимого (настоящий контент и новый контент для записи). Метод copyOf из класса Arrays (follow the link) выделяет пространство для нового буфера, копирует содержимое старого буфера в новый и возвращает его в grow.

Ваша проблема возникает при распределении пространства для нового буфера. После некоторого write вы попали в точку, в которой исчерпана доступная память: java.lang.OutOfMemoryError: Java heap space.

Если посмотреть в деталь, вы читаете на кусках 2048. Так

  • вашей первая запись на выращивает размер буфера от 32 до 2048
  • второго вызова удвоит его 2 * 2048
  • Ваш третий вызов займет 2^2 * 2048, вы должны время написать еще два раза перед необходимостью выделения.
  • , затем 2^3 * 2048, у вас будет время для написания 4 mores, прежде чем выделять снова.
  • В какой-то момент ваш буфер будет иметь размер 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 ГБ.

см

0

Подумав об этом, я решил поставить второй ответ. Я рассмотрел преимущества и недостатки ответа на этот второй ответ, и преимущества этого стоит. Так вот оно.

Большинство предлагаемых соображений забывают об одном факте: существует встроенный предел в размере массивов (включая ByteArrayOutputStream), которые вы можете использовать в Java. И этот предел диктуется самым большим значением int, которое составляет 2^31 - 1 (немного меньше 2Giga). Это означает, что вы можете читать максимум 2 ГБ (-1 байт) и помещать его в один ByteArrayOutputStream. Предел может быть меньше размера массива, если VM хочет большего контроля.

Мое предложение состоит в том, чтобы использовать ArrayList из 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's 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

Смежные вопросы