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
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
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
@Luffy ma sens, aby odpowiedzieć na to pytanie, nie wiedząc ** dlaczego ** tak dużo danych jest odczytywanych do pamięci? – k3b