Wiem, że każdy obiekt wymaga pamięci stosu, a każda operacja podstawowa / referencja na stosie wymaga pamięci stosu.
Gdy próbuję utworzyć obiekt na stercie i nie ma wystarczającej pamięci, aby to zrobić, JVM tworzy błąd java.lang.OutOfMemoryError na stercie i rzuca mi go.
Tak więc domyślnie oznacza to, że JVM ma trochę pamięci zarezerwowanej podczas uruchamiania.
Co się stanie, gdy ta zarezerwowana pamięć zostanie zużyta (zdecydowanie by się zużyła, przeczytaj dyskusję poniżej) i JVM nie ma wystarczającej ilości pamięci na stercie, aby utworzyć instancję java.lang.OutOfMemoryError ?
Czy to po prostu się zawiesza? A może rzuci mnie, null
skoro nie ma pamięci dla new
instancji OOM?
try {
Object o = new Object();
// and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
// JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
// what next? hangs here, stuck forever?
// or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}
==
Dlaczego JVM nie może zarezerwować wystarczającej ilości pamięci?
Bez względu na to, ile pamięci jest zarezerwowane, nadal można wykorzystać tę pamięć, jeśli JVM nie ma możliwości „odzyskania” tej pamięci:
try {
Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
// JVM had 100 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
// JVM had 99 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e3) {
// JVM had 98 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e4) {
// JVM had 97 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e5) {
// JVM had 96 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e6) {
// JVM had 95 units of "spare memory". 1 is used to create this OOM.
e.printStackTrace();
//........the JVM can't have infinite reserved memory, he's going to run out in the end
}
}
}
}
}
}
Lub bardziej zwięźle:
private void OnOOM(java.lang.OutOfMemoryError e) {
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
OnOOM(e2);
}
}
źródło
OutOfMemoryException
a następnie robiła coś, co wymagało utworzenia dużego bufora ...OutOfMemoryError
i zachowa odniesienie do niej. Okazuje się, że złapanie aOutOfMemoryError
nie jest tak przydatne, jak mogłoby się wydawać, ponieważ nie można zagwarantować prawie nic ze stanu programu, kiedy go złapie. Zobacz stackoverflow.com/questions/8728866/…Odpowiedzi:
W JVM nigdy tak naprawdę brakuje pamięci. Z góry wykonuje obliczenia pamięci stosu.
Struktura JVM, rozdział 3 , sekcja 3.5.2 stanowi:
W przypadku sterty , sekcja 3.5.3.
Tak więc wykonuje obliczenia z wyprzedzeniem przed przydzieleniem obiektu.
Co się dzieje, JVM próbuje przydzielić pamięć dla obiektu w pamięci o nazwie Region generacji stałej (lub PermSpace). Jeśli alokacja nie powiedzie się (nawet po tym, jak JVM wywoła Garbage Collector, aby spróbować i przydzielić wolne miejsce), zgłasza
OutOfMemoryError
. Nawet wyjątki wymagają miejsca w pamięci, więc błąd zostanie zgłoszony w nieskończoność.Dalsza lektura. ? Ponadto
OutOfMemoryError
może występować w innej strukturze JVM.źródło
Graham Borland wydaje się mieć rację : przynajmniej moja JVM ponownie używa OutOfMemoryErrors. Aby to przetestować, napisałem prosty program testowy:
Po uruchomieniu generuje on następujące dane wyjściowe:
BTW, JVM, którą uruchamiam (na Ubuntu 10.04) jest następujący:
Edycja: Próbowałem zobaczyć, co by się stało, gdybym zmusił maszynę JVM do całkowitego braku pamięci za pomocą następującego programu:
Jak się okazuje, wydaje się, że zapętla się w nieskończoność. Co ciekawe, próba zakończenia programu za pomocą Ctrl+ Cnie działa, ale daje tylko następujący komunikat:
Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated
źródło
n
OOM i ponowne użycie jednego z nich później, aby OOM zawsze mógł zostać wyrzucony. JVM firmy Sun / Oracle nie obsługuje rekurencji ogona w ogóle w IIRC?n
ramki na stosie i kończy się tworzeniem i niszczeniem ramkin+1
na wieczność, dając wrażenie ciągłego działania.Większość środowisk wykonawczych wstępnie przydzieli podczas uruchamiania lub w inny sposób zarezerwuje wystarczającą ilość pamięci, aby poradzić sobie z sytuacjami głodu pamięci. Myślę, że większość rozsądnych implementacji JVM by to zrobiła.
źródło
catch
klauzula będzie próbowała wykorzystać więcej pamięci, JVM może po prostu ciągle rzucać tą samą instancją OOM.Ostatnim razem, gdy pracowałem w Javie i korzystałem z debuggera, inspektor sterty wykazał, że JVM przydzielił wystąpienie OutOfMemoryError podczas uruchamiania. Innymi słowy, przydziela obiekt, zanim Twój program będzie miał szansę na zajęcie, a co dopiero zabraknie pamięci.
źródło
Ze specyfikacji JVM, rozdział 3.5.2:
Każda wirtualna maszyna Java musi zagwarantować, że wyrzuci
OutOfMemoryError
. Oznacza to, że musi być w stanie utworzyć wystąpienieOutOfMemoryError
(lub mieć utworzone wcześniej), nawet jeśli nie ma już miejsca na stosie.Chociaż nie musi to gwarantować, że jest wystarczająca ilość pamięci, aby go złapać i wydrukować ładny ślad stosu ...
Dodanie
Dodano kod, aby pokazać, że JVM może zabraknąć miejsca na sterty, jeśli będzie musiał wyrzucić więcej niż jeden
OutOfMemoryError
. Ale taka implementacja naruszyłaby powyższy wymóg.Nie ma wymogu, aby zgłaszane wystąpienia
OutOfMemoryError
były unikalne lub tworzone na żądanie. JVM może przygotować dokładnie jedno wystąpienieOutOfMemoryError
podczas uruchamiania i wyrzucić je za każdym razem, gdy zabraknie miejsca na stercie - to jest raz w normalnym środowisku. Innymi słowy: przykłademOutOfMemoryError
tego może być singleton.źródło
Interesujące pytanie :-). Podczas gdy inni dobrze wyjaśnili aspekty teoretyczne, postanowiłem to wypróbować. To jest na Oracle JDK 1.6.0_26, Windows 7 64 bit.
Konfiguracja testowa
Napisałem prosty program do wyczerpania pamięci (patrz poniżej).
Program po prostu tworzy statyczny
java.util.List
i utrzymuje w nim świeże ciągi, aż do momentu wyrzucenia OOM. Następnie łapie go i kontynuuje upychanie w nieskończonej pętli (słaba JVM ...).Wynik testu
Jak widać z danych wyjściowych, pierwsze cztery przypadki wyrzucenia OOME zawierają ślad stosu. Następnie kolejne OOME drukują tylko,
java.lang.OutOfMemoryError: Java heap space
jeśliprintStackTrace()
wywołaniu.Najwyraźniej JVM próbuje wydrukować ślad stosu, jeśli może, ale jeśli pamięć jest naprawdę napięta, po prostu pomija ślad, tak jak sugerują inne odpowiedzi.
Interesujący jest również kod skrótu OOME. Zauważ, że kilka pierwszych OOME ma różne skróty. Gdy JVM zacznie pomijać ślady stosu, skrót jest zawsze taki sam. Sugeruje to, że JVM będzie używał nowych (wstępnie przydzielonych?) Instancji OOME tak długo, jak to możliwe, ale jeśli push zostanie wypchnięty, po prostu użyje ponownie tej samej instancji, zamiast mieć nic do rzucenia.
Wynik
Uwaga: Obciąłem niektóre ślady stosu, aby ułatwić odczyt danych wyjściowych („[...]”).
Program
źródło
System.out
aleprintStackTrace()
używaSystem.err
domyślnie. Prawdopodobnie uzyskasz lepsze wyniki, stosując konsekwentnie jeden z tych strumieni.Jestem całkiem pewien, że JVM upewni się, że ma przynajmniej wystarczającą ilość pamięci, aby zgłosić wyjątek, zanim zabraknie pamięci.
źródło
Wyjątki wskazujące na próbę przekroczenia granic środowiska pamięci zarządzanej są obsługiwane przez środowisko wykonawcze tego środowiska, w tym przypadku JVM. JVM jest własnym procesem, w którym działa IL aplikacji. Jeśli program podejmie próbę wywołania, które przedłuży stos wywołań poza limity lub przydzieli więcej pamięci niż JVM może zarezerwować, środowisko wykonawcze wprowadzi wyjątek, który spowoduje rozwinięcie stosu wywołań. Bez względu na ilość pamięci, jakiej aktualnie potrzebuje Twój program, lub jak głęboki jest stos wywołań, JVM przydzieli wystarczającą ilość pamięci w obrębie własnych granic procesu, aby utworzyć wspomniany wyjątek i wstrzyknąć go do kodu.
źródło
Wygląda na to, że mylisz pamięć wirtualną zarezerwowaną przez JVM, w której JVM uruchamia programy Java, z rodzimą pamięcią systemu operacyjnego hosta, w której JVM działa jako proces natywny. JVM na twoim komputerze działa w pamięci zarządzanej przez system operacyjny, a nie w pamięci zarezerwowanej przez JVM do uruchamiania programów Java.
Dalsza lektura:
I jako ostatnia uwaga, próbując złapać się java.lang.Error (i jego potomka klasach), aby wydrukować StackTrace mogą nie daje żadnych użytecznych informacji. Zamiast tego chcesz zrzucić stos.
źródło
W celu dalszego wyjaśnienia odpowiedzi @Graham Borland funkcjonalnie JVM robi to podczas uruchamiania:
Później JVM wykonuje jeden z następujących kodów bajtowych Java: „new”, „anewarray” lub „multianewarray”. Ta instrukcja powoduje, że JVM wykonuje szereg kroków w przypadku braku pamięci:
allocate()
.allocate()
próbuje przydzielić pamięć dla nowej instancji określonej klasy lub tablicy.doGC()
, , która próbuje wykonać odśmiecanie.allocate()
próbuje ponownie przydzielić pamięć dla instancji.throw OOME;
funkcji replace (), po prostu robi a , odnosząc się do OOME, który utworzył przy uruchomieniu. Zauważ, że nie musiał on alokować tego OOME, on po prostu odnosi się do niego.Oczywiście nie są to dosłowne kroki; będą się różnić między implementacjami JVM i JVM, ale jest to pomysł na wysokim poziomie.
(*) Przed porażką dzieje się tutaj znaczna ilość pracy. JVM podejmie próbę wyczyszczenia obiektów SoftReference, spróbuje alokacji bezpośrednio do pokrewnej generacji podczas korzystania z kolektora generacji i ewentualnie innych rzeczy, takich jak finalizacja.
źródło
Odpowiedzi, które mówią, że JVM wstępnie przydzieli,
OutOfMemoryErrors
są rzeczywiście poprawne.Oprócz przetestowania tego przez wywołanie sytuacji braku pamięci, możemy po prostu sprawdzić stertę dowolnej maszyny JVM (użyłem małego programu, który po prostu się przespał, uruchamiając go przy użyciu JVM Hotspot Oracle z aktualizacji Java 8 31).
Za pomocą
jmap
widzimy, że wydaje się, że istnieje 9 instancji OutOfMemoryError (mimo że mamy dużo pamięci):Następnie możemy wygenerować zrzut stosu:
i otwórz go za pomocą Eclipse Memory Analyzer , gdzie zapytanie OQL pokazuje, że JVM faktycznie wydaje się wstępnie przydzielać
OutOfMemoryErrors
dla wszystkich możliwych komunikatów:Kod JVM Hotspot Java 8, który faktycznie wstępnie przydziela je, można znaleźć tutaj i wygląda następująco (z pominięciem niektórych części):
i ten kod pokazuje, że JVM najpierw spróbuje użyć jednego z wstępnie przydzielonych błędów ze spacją dla śledzenia stosu, a następnie powróci do jednego bez śledzenia stosu:
źródło
Metaspace
. Byłoby miło, gdybyś mógł pokazać fragment kodu, który obsługuje również PermGen, i porównać go również z Metaspace.