2013-08-29 11 views
15

Jestem nowicjuszem w Javie i pracuję nad czytaniem bardzo dużych plików, potrzebuję pomocy w zrozumieniu problemu i jego rozwiązaniu. Mamy starsze kody, które muszą być zoptymalizowane, aby działały poprawnie. Rozmiar pliku może wynosić od 10 do 10 gb. tylko problem zaczyna się, gdy plik zaczyna się od rozmiaru 800mb.Wystąpienie błędu Java OutOfMemoryError podczas czytania dużego pliku tekstowego

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

Jesteśmy coraz błąd

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) 

Każda pomoc będzie mile widziane?

+1

W przykładowym kodzie, o którym wspomniałeś, po prostu ładujesz cały plik w 'ByteArrayOutputStream'. Jaki jest przypadek użycia? Czy naprawdę potrzebne są całe dane pliku w 'byte []'? – Santosh

+0

Czy możesz dać mi znać, której wersji JDK zamierzasz użyć, mam inne rozwiązanie dla JDK 8 i JDK7 lub mniejszego. – Bhupi

+0

@Luffy ma sens, aby odpowiedzieć na to pytanie, nie wiedząc ** dlaczego ** tak dużo danych jest odczytywanych do pamięci? – k3b

Odpowiedz

7

Wirtualna maszyna Javy (JVM) prowadzi z górnego limitu stałej pamięci, którą można zmodyfikować w następujący sposób:

java -Xmx1024m .... 

np powyższa opcja (-Xmx ...) ustawia limit 1024 megabajtów. Możesz zmienić w razie potrzeby (w granicach swojego komputera, systemu operacyjnego itd.). Zauważ, że różni się to od tradycyjnych aplikacji, które przydzielają coraz więcej pamięci z systemu operacyjnego na żądanie.

Jednak lepszym rozwiązaniem jest przerobienie aplikacji, tak aby nie trzeba było ładować do pamięci całego pliku w całości za pomocą jednego pliku. W ten sposób nie musisz dostroić maszyny JVM i nie musisz narzucać ogromnego miejsca na pamięć.

4

Nie można odczytać pliku tekstowego 10GB w pamięci. Najpierw musisz przeczytać X MB, zrób coś z tym i przeczytaj kolejne X MB.

+3

Jeśli ma 10 Gb i 64-bitową maszynę JVM, może * to zrobić. Prawdopodobnie nie powinien. –

+0

jakiejkolwiek pomocy dotyczącej czytania w partycjach? –

+0

@Brian Nie, nie może. Nawet w wersji 64-bitowej możliwa jest tylko ograniczona liczba elementów w tablicy. – sigi

3

ByteArrayOutputStream zapisuje do bufora w pamięci. Jeśli tak naprawdę chcesz, aby to działało, musisz zmienić wielkość sterty JVM po maksymalnym możliwym rozmiarze wejścia. Ponadto, jeśli to możliwe, możesz sprawdzić rozmiar wejściowy przed rozpoczęciem przetwarzania, aby zaoszczędzić czas i zasoby.

Alternatywne podejście to rozwiązanie strumieniowe, w którym znana jest ilość pamięci używanej w środowisku wykonawczym (może być konfigurowalna, ale wciąż znana przed uruchomieniem programu), ale jeśli jest to możliwe, zależy całkowicie od domeny aplikacji (ponieważ można nie używa już bufora w pamięci) i może architekturę reszty kodu, jeśli nie możesz/nie chcesz tego zmieniać.

4

Spróbuj użyć dużego rozmiaru bufora odczytu, który może wynosić 10 MB, a następnie sprawdź.

4

Problem tkwi w tym, co robisz. Czytanie całych plików w pamięci jest zawsze i wszędzie złym pomysłem. Naprawdę nie będziecie w stanie odczytać pliku o pojemności 10 GB w pamięci przy użyciu aktualnej technologii, chyba że macie dość zaskakujący sprzęt. Znajdź sposób, aby przetworzyć je wiersz po wierszu, nagrać według rekordu, porcję po kawałku, ...

+0

"Czytanie całych plików w pamięci jest zawsze i wszędzie złym pomysłem"? Powiedz to mojemu wydawcy! :-) –

17

Spróbuj użyć java.nio.MappedByteBuffer.

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

Można mapować zawartość z plikiem na pamięć bez kopiowania go ręcznie. Wysokopoziomowe systemy operacyjne oferują mapowanie pamięci, a Java ma interfejs API do korzystania z tej funkcji.

Jeśli moje rozumowanie jest poprawne, mapowanie pamięci nie ładuje całej zawartości pliku do pamięci (co oznacza "załadowane i rozładowane częściowo w razie potrzeby"), więc domyślam się, że plik o pojemności 10 GB nie pożera twojej pamięci.

4

Czy przesyłanie całego strumienia wyjściowego jest obowiązkowe dla całego ByteArray()?

byte[] data = bArrStream.toByteArray(); 

najlepszym podejściem jest czytany linia po linii & napisać to linia po linii. Możesz użyć BufferedReader lub Scanner, aby przeczytać duże pliki, jak poniżej.

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

    } 
} 

Można zastąpić System.out z ByteArrayOutputStream w powyższym przykładzie.

Proszę spojrzeć na poniższym artykule Więcej szczegółów: Read Large File

rzucić okiem na powiązanym SE pytanie:

Scanner vs. BufferedReader

11

Chociaż można zwiększyć limit pamięci JVM, jest niepotrzebny i przydzielenie ogromnej pamięci, takiej jak 10 GB, w celu przetworzenia nadmiernych dźwięków pliku i intensywnego korzystania z zasobów.

Obecnie używasz "ByteArrayOutputStream", który utrzymuje pamięć wewnętrzną do przechowywania danych. Ta linia w kodzie wciąż dołączenie ostatniego odczytu plików 2KB klocek do końca tego bufora:

bArrStream.write(localbuffer, 0, i); 

bArrStream rośnie iw końcu zabraknie pamięci.

Zamiast tego należy zreorganizować swój algorytm i przetworzyć pliku w sposób Streaming:

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

Hi jestem przy założeniu, że czytasz dużego pliku txt, a dane jest ustawiona linia po linii, użyj wiersz po wierszu podejście do czytania. Jak wiem, możesz przeczytać do 6 GB może być więcej. Gorąco radzę ci wypróbować to podejście.

DATA1 dane2 ...

// 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

Przeczytaj plik iteracyjnie linewise. To znacznie zmniejszyłoby zużycie pamięci. Alternatywnie można użyć

FileUtils.lineIterator (thefile, "UTF-8");

dostarczone przez 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 z opcją wiersza polecenia -Xmx, który określa maksymalną wielkość sterty.

See here for details..

+0

Ten link nie działa dla mnie, czy możesz podać ważne informacje tutaj, oprócz linku? – innoSPG

2

należy zwiększyć wielkość sterty jak podano w następującej odpowiedzi:

Increase heap size in Java

Należy jednak pamiętać, że środowisko wykonawcze Java i kodować trochę miejsca, a więc dodać bufor do pożądane maksimum.

2

Krótka odpowiedź,

nie robiąc nic, można przesunąć limit prądu przez współczynnik 1,5. Oznacza to, że jeśli potrafisz przetworzyć 800MB, możesz przetworzyć 1200 MB. Oznacza to również, że jeśli podstępem jest java -Xm ...., możesz przejść do punktu, w którym twój obecny kod może przetworzyć 7 GB, problem zostanie rozwiązany, ponieważ współczynnik 1.5 przeniesie Cię do 10,5 GB, zakładając, że masz dostępną przestrzeń w systemie i że JVM może to dostać.

Długa odpowiedź:

Błąd jest dość samo-opisowy. Uderzyłeś w praktyczne ograniczenie pamięci w swojej konfiguracji. Istnieje wiele spekulacji na temat limitu, jaki możesz mieć z JVM, nie wiem wystarczająco dużo o tym, ponieważ nie mogę znaleźć żadnych oficjalnych informacji. Będziesz jednak w jakiś sposób ograniczony przez ograniczenia, takie jak dostępna zamiana, użycie przestrzeni adresowej jądra, fragmentacja pamięci itp.

Co się dzieje teraz, to, że obiekty są tworzone z domyślnym buforem o wielkości 32, jeśli to zrobisz nie dostarcza żadnego rozmiaru (jest to twój przypadek). Za każdym razem, gdy wywołujesz metodę write na obiekcie, uruchamiana jest wewnętrzna maszyna. Model openjdk implementation release 7u40-b43, który wydaje się idealnie pasować do wyjścia Twojego błędu, używa wewnętrznej metody ensureCapacity, aby sprawdzić, czy bufor ma wystarczająco dużo miejsca na umieszczenie bajtów, które chcesz zapisać. Jeśli nie ma wystarczającej ilości miejsca, wywoływana jest inna metoda wewnętrzna, aby zwiększyć rozmiar bufora. Metoda grow definiuje odpowiedni rozmiar i wywołuje metodę copyOf z klasy, aby wykonać zadanie. Odpowiedni rozmiar bufora to maksymalny rozmiar między bieżącym rozmiarem i rozmiarem wymaganym do przechowywania całej zawartości (obecnej zawartości i nowej treści do zapisania). Metoda copyOf z klasy Arrays (follow the link) przydziela miejsce dla nowego bufora, kopiuje zawartość starego bufora do nowego i zwraca je do grow.

Twój problem pojawia się podczas przydzielania miejsca na nowy bufor. Po upływie pewnego czasu write dojdziesz do punktu, w którym wyczerpana jest dostępna pamięć: java.lang.OutOfMemoryError: Java heap space.

Jeśli spojrzymy w szczegóły, czytasz przez kawałkami 2048. Więc

  • Twoja pierwsza napisać do rośnie wielkość bufora od 32 do 2048
  • drugiego wezwania podwoi go 2 * 2048
  • twoje trzecie połączenie przeniesie je do 2^2 * 2048, musisz napisać jeszcze dwa razy przed koniecznością przydzielenia.
  • następnie 2^3 * 2048, będziesz mieć czas na 4 zapisy na pamięć przed ponownym przydzieleniem.
  • w pewnym momencie twój bufor będzie miał rozmiar 2^18 * 2048, który jest 2^19 * 1024 lub 2^9 * 2^20 (512 MB)
  • następnie 2^19 * 2048, który jest 1024 MB lub 1 GB

W twoim opisie jest niejasne, że możesz w jakiś sposób odczytać do 800 MB, ale nie możesz wyjść poza to. Musisz mi to wyjaśnić.

Oczekuję, że twój limit będzie dokładnie równa 2 (lub mniej, jeśli użyjemy mniej niż 10 jednostek). W związku z tym oczekuję, że zaczniesz mieć problemy natychmiast po przekroczeniu jednego z nich: 256 MB, 512 MB, 1 GB, 2 GB itp.

Po przekroczeniu tego limitu nie oznacza to, że brakuje Ci pamięci, oznacza po prostu, że nie można przydzielić innego bufora dwukrotnie większego niż bufor, który już posiadasz. Obserwacja ta otwiera pole do poprawy w swojej pracy: znaleźć maksymalny rozmiar buforu, który można przeznaczyć i zarezerwować go upfront wywołując odpowiedni konstruktor

ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(myMaxSize); 

to ma tę zaletę, że zmniejszenie szczytowego alokacji pamięci tło, co dzieje się pod kaptur, aby Cię uszczęśliwić. Robiąc to, będziesz mógł przejść do 1,5 limitu, który masz teraz. Jest tak po prostu dlatego, że po raz ostatni bufor został zwiększony, przeszedł z połowy bieżącego rozmiaru do bieżącego rozmiaru, aw pewnym momencie w pamięci był obecny zarówno bieżący, jak i stary. Ale nie będziesz w stanie przekroczyć 3-krotnego limitu, jaki masz teraz. Wyjaśnienie jest dokładnie takie samo.

Powiedziałem, że nie mam żadnej magicznej sugestii, aby rozwiązać problem, oprócz przetwarzania danych przez porcje o danej wielkości, po jednym kawałku na raz. Innym dobrym podejściem będzie użycie sugestii Takahiko Kawasaki i użycie MappedByteBuffer. Pamiętaj, że w każdym przypadku będziesz potrzebował co najmniej 10 GB pamięci fizycznej lub pamięci wymiany, aby móc załadować plik o pojemności 10 GB.

zobacz

0

Po przemyśleniu tego zdecydowałem się udzielić drugiej odpowiedzi. Rozważyłem zalety i wady tej drugiej odpowiedzi, a korzyści są warte podjęcia. Więc oto jest.

Większość sugerowanych rozważań polega na tym, że zapomina się o danym fakcie: Istnieje wbudowany limit wielkości tablic (w tym ByteArrayOutputStream), które można mieć w Javie. Ten limit jest podyktowany największą wartością, która wynosi 2^31 - 1 (trochę mniej niż 2Giga). Oznacza to, że możesz odczytać maksymalnie 2 GB (1 bajt) i umieścić go w jednym kodzie ByteArrayOutputStream. Limit może faktycznie być mniejszy dla rozmiaru tablicy, jeśli VM chce większej kontroli.

Moja sugestia to użycie numeru ArrayList z byte[] zamiast pojedynczego byte[] zawierającego pełną treść pliku. A także usuń niepotrzebny krok umieszczania w ByteArrayOutputStream przed umieszczeniem go w ostatecznej tablicy . Oto przykład na podstawie oryginalnego kodu:

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 

prostu działa Twój program powinien działać na systemie 64-bitowym z wystarczającą ilością pamięci fizycznej lub wymiany. Teraz, jeśli chcesz przyspieszyć, aby pomóc VM rozmiar poprawnie sterty na początku, uruchom z opcjami -Xms i -Xmx.Na przykład, jeśli chcesz, aby stertę 12 GB można było przetworzyć w pliku 10 GB, użyj java -Xms12288m -Xmx12288m YourApp

Powiązane problemy