Polowanie na wycieki pamięci Java

Opublikowany: 2022-03-11

Niedoświadczeni programiści często myślą, że automatyczne usuwanie śmieci w Javie całkowicie uwalnia ich od martwienia się o zarządzanie pamięcią. Jest to powszechne błędne wyobrażenie: podczas gdy garbage collector robi wszystko, co w jego mocy, nawet najlepszy programista może paść ofiarą paraliżujących wycieków pamięci. Pozwól mi wyjaśnić.

Przeciek pamięci występuje, gdy odwołania do obiektów, które nie są już potrzebne, są niepotrzebnie utrzymywane. Te przecieki są złe. Po pierwsze, wywierają niepotrzebną presję na komputer, ponieważ programy zużywają coraz więcej zasobów. Co gorsza, wykrycie tych wycieków może być trudne: analiza statyczna często ma trudności z dokładną identyfikacją tych nadmiarowych referencji, a istniejące narzędzia do wykrywania wycieków śledzą i raportują szczegółowe informacje o poszczególnych obiektach, dając wyniki trudne do interpretacji i pozbawione precyzji.

Innymi słowy, przecieki są albo zbyt trudne do zidentyfikowania, albo identyfikowane w sposób zbyt szczegółowy, aby był użyteczny.

W rzeczywistości istnieją cztery kategorie problemów z pamięcią z podobnymi i nakładającymi się objawami, ale z różnymi przyczynami i rozwiązaniami:

  • Wydajność : zwykle związana z nadmiernym tworzeniem i usuwaniem obiektów, dużymi opóźnieniami w usuwaniu elementów bezużytecznych, nadmierną zamianą stron systemu operacyjnego i nie tylko.

  • Ograniczenia zasobów : występują, gdy jest zbyt mało dostępnej pamięci lub pamięć jest zbyt pofragmentowana, aby przydzielić duży obiekt — może to być natywne lub, częściej, związane ze stertą Java.

  • Wycieki sterty Java : klasyczny wyciek pamięci, w którym obiekty Java są tworzone w sposób ciągły bez ich zwalniania. Jest to zwykle spowodowane ukrytymi odniesieniami do obiektów.

  • Wycieki pamięci natywnej : związane ze stale rosnącym wykorzystaniem pamięci, które znajduje się poza stertą Java, takie jak alokacje dokonywane przez kod JNI, sterowniki, a nawet alokacje JVM.

W tym samouczku dotyczącym zarządzania pamięcią skupię się na wyciekach stert Java i przedstawię podejście do wykrywania takich wycieków na podstawie raportów Java VisualVM i wykorzystania wizualnego interfejsu do analizy aplikacji opartych na technologii Java podczas ich działania.

Ale zanim będziesz mógł zapobiegać i znajdować wycieki pamięci, powinieneś zrozumieć, jak i dlaczego one występują. ( Uwaga: jeśli dobrze rozumiesz zawiłości wycieków pamięci, możesz przejść dalej).

Wycieki pamięci: elementarz

Na początek potraktuj wyciek pamięci jako chorobę, a OutOfMemoryError (OOM) jako objaw. Ale tak jak w przypadku każdej choroby, nie wszystkie OOM muszą koniecznie oznaczać wycieki pamięci : OOM może wystąpić z powodu generowania dużej liczby zmiennych lokalnych lub innych podobnych zdarzeń. Z drugiej strony, nie wszystkie wycieki pamięci muszą objawiać się jako OOM , szczególnie w przypadku aplikacji desktopowych lub aplikacji klienckich (które nie działają zbyt długo bez restartów).

Pomyśl o wycieku pamięci jako o chorobie, a OutOfMemoryError jako o symptomie. Ale nie wszystkie OutOfMemoryErrors oznaczają przecieki pamięci i nie wszystkie przecieki pamięci objawiają się jako OutOfMemoryErrors.

Dlaczego te przecieki są takie złe? Między innymi, wyciekanie bloków pamięci podczas wykonywania programu często obniża wydajność systemu w czasie, ponieważ przydzielone, ale nieużywane bloki pamięci będą musiały zostać zamienione, gdy systemowi zabraknie wolnej pamięci fizycznej. W końcu program może nawet wyczerpać dostępną wirtualną przestrzeń adresową, prowadząc do OOM.

Rozszyfrowanie OutOfMemoryError

Jak wspomniano powyżej, OOM jest powszechnym wskaźnikiem wycieku pamięci. Zasadniczo błąd jest zgłaszany, gdy nie ma wystarczającej ilości miejsca do przydzielenia nowego obiektu. Spróbuj, ale śmieciarz nie może znaleźć potrzebnej przestrzeni, a hałda nie może być dalej rozbudowywana. W ten sposób pojawia się błąd wraz ze śladem stosu.

Pierwszym krokiem w diagnozowaniu OOM jest ustalenie, co faktycznie oznacza błąd. Brzmi to oczywiste, ale odpowiedź nie zawsze jest tak jasna. Na przykład: Czy OOM pojawia się, ponieważ sterta Java jest pełna, czy też sterta natywna jest pełna? Aby pomóc Ci odpowiedzieć na to pytanie, przeanalizujmy kilka możliwych komunikatów o błędach:

  • java.lang.OutOfMemoryError: Java heap space

  • java.lang.OutOfMemoryError: PermGen space

  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit

  • java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?

  • java.lang.OutOfMemoryError: <reason> <stack trace> (Native method)

„Przestrzeń sterty Java”

Ten komunikat o błędzie niekoniecznie oznacza przeciek pamięci. W rzeczywistości problem może być tak prosty, jak kwestia konfiguracji.

Na przykład byłem odpowiedzialny za analizę aplikacji, która konsekwentnie produkowała tego typu OutOfMemoryError . Po pewnym dochodzeniu odkryłem, że winowajcą była instancja tablicy, która wymagała zbyt dużej ilości pamięci; w tym przypadku nie była to wina aplikacji, ale raczej serwer aplikacji polegał na domyślnym rozmiarze sterty, który był zbyt mały. Problem rozwiązałem dostosowując parametry pamięci JVM.

W innych przypadkach, zwłaszcza w przypadku aplikacji długowiecznych, komunikat może wskazywać, że nieumyślnie przechowujemy odniesienia do obiektów , co uniemożliwia garbage collectorowi ich wyczyszczenie. Jest to odpowiednik wycieku pamięci w języku Java . ( Uwaga: interfejsy API wywoływane przez aplikację mogą również przypadkowo przechowywać odwołania do obiektów).

Innym potencjalnym źródłem tych OOM-ów „przestrzeni sterty Java” jest użycie finalizatorów . Jeśli klasa ma metodę finalize , obiekty tego typu nie mają odzyskiwanej przestrzeni w czasie wyrzucania elementów bezużytecznych. Zamiast tego po wyczyszczeniu elementów bezużytecznych obiekty są umieszczane w kolejce do finalizacji, która następuje później. W implementacji Sun finalizatory są wykonywane przez wątek demona. Jeśli wątek finalizatora nie nadąża za kolejką finalizacji, sterta Java może się zapełnić i może zostać zgłoszony OOM.

„Przestrzeń PermGenu”

Ten komunikat o błędzie wskazuje, że stałe generowanie jest pełne. Trwałe generowanie to obszar sterty, w którym przechowywane są obiekty klas i metod. Jeśli aplikacja ładuje dużą liczbę klas, może być konieczne zwiększenie rozmiaru stałego generowania przy użyciu opcji -XX:MaxPermSize .

Internowane obiekty java.lang.String są również przechowywane w stałej generacji. Klasa java.lang.String utrzymuje pulę ciągów. Gdy wywoływana jest metoda intern, metoda sprawdza pulę, aby sprawdzić, czy istnieje równoważny ciąg. Jeśli tak, to jest zwracane przez metodę stażysty; jeśli nie, ciąg jest dodawany do puli. Mówiąc bardziej precyzyjnie, metoda java.lang.String.intern zwraca reprezentację kanoniczną ciągu; wynikiem jest odwołanie do tej samej instancji klasy, która zostałaby zwrócona, gdyby ten ciąg pojawił się jako literał. Jeśli aplikacja internuje dużą liczbę ciągów, może być konieczne zwiększenie rozmiaru stałego generowania.

Uwaga: możesz użyć polecenia jmap -permgen aby wyświetlić statystyki związane z generowaniem trwałym, w tym informacje o zinternalizowanych instancjach String.

„Żądany rozmiar tablicy przekracza limit maszyny wirtualnej”

Ten błąd wskazuje, że aplikacja (lub interfejsy API używane przez tę aplikację) próbowała przydzielić tablicę większą niż rozmiar sterty. Na przykład, jeśli aplikacja próbuje przydzielić tablicę o wielkości 512 MB, ale maksymalny rozmiar sterty wynosi 256 MB, z tym komunikatem o błędzie zostanie wygenerowany komunikat OOM. W większości przypadków problemem jest albo problem z konfiguracją, albo błąd, który pojawia się, gdy aplikacja próbuje przydzielić ogromną macierz.

„Poproś o <rozmiar> bajtów z powodu <przyczyny>. Brak miejsca na wymianę?

Ten komunikat wydaje się być OOM. Jednak maszyna wirtualna HotSpot zgłasza ten pozorny wyjątek, gdy alokacja ze sterty natywnej nie powiodła się, a sterta natywna może być bliska wyczerpania. Komunikat zawiera rozmiar (w bajtach) żądania, które nie powiodło się, oraz przyczynę żądania pamięci. W większości przypadków <przyczyna> to nazwa modułu źródłowego, który zgłasza błąd alokacji.

Jeśli zostanie zgłoszony ten typ OOM, może być konieczne użycie narzędzi do rozwiązywania problemów w systemie operacyjnym w celu dalszego zdiagnozowania problemu. W niektórych przypadkach problem może nawet nie być związany z aplikacją. Na przykład możesz zobaczyć ten błąd, jeśli:

  • System operacyjny jest skonfigurowany z niewystarczającą przestrzenią wymiany.

  • Inny proces w systemie zużywa wszystkie dostępne zasoby pamięci.

Możliwe jest również, że aplikacja nie powiodła się z powodu natywnego przecieku (na przykład, jeśli jakiś fragment kodu aplikacji lub biblioteki stale przydziela pamięć, ale nie zwalnia jej do systemu operacyjnego).

<przyczyna> <śledzenie stosu> (metoda natywna)

Jeśli zobaczysz ten komunikat o błędzie, a górna ramka śledzenia stosu jest metodą natywną, oznacza to, że ta metoda natywna napotkała błąd alokacji. Różnica między tym komunikatem a poprzednim polega na tym, że błąd alokacji pamięci Java został wykryty w JNI lub metodzie natywnej, a nie w kodzie Java VM.

Jeśli zostanie zgłoszony ten typ OOM, może być konieczne użycie narzędzi w systemie operacyjnym w celu dalszego zdiagnozowania problemu.

Awaria aplikacji bez OOM

Czasami aplikacja może ulec awarii wkrótce po niepowodzeniu alokacji ze sterty natywnej. Dzieje się tak, jeśli używasz kodu natywnego, który nie sprawdza błędów zwracanych przez funkcje alokacji pamięci.

Na przykład wywołanie systemowe malloc zwraca NULL , jeśli nie ma dostępnej pamięci. Jeśli zwrot z malloc nie jest zaznaczony, aplikacja może ulec awarii podczas próby uzyskania dostępu do nieprawidłowej lokalizacji w pamięci. W zależności od okoliczności tego typu problem może być trudny do zlokalizowania.

W niektórych przypadkach wystarczą informacje z dziennika błędów krytycznych lub zrzutu awaryjnego. Jeśli przyczyną awarii jest brak obsługi błędów w niektórych alokacjach pamięci, musisz znaleźć przyczynę tego niepowodzenia alokacji. Podobnie jak w przypadku każdego innego problemu ze stertą natywną, system może być skonfigurowany z niewystarczającą przestrzenią wymiany, inny proces może zużywać wszystkie dostępne zasoby pamięci itp.

Diagnozowanie przecieków

W większości przypadków diagnozowanie wycieków pamięci wymaga bardzo szczegółowej znajomości danej aplikacji. Ostrzeżenie: proces może być długi i powtarzalny.

Nasza strategia wykrywania wycieków pamięci będzie stosunkowo prosta:

  1. Zidentyfikuj objawy

  2. Włącz szczegółowe zbieranie śmieci

  3. Włącz profilowanie

  4. Przeanalizuj ślad

1. Zidentyfikuj objawy

Jak wspomniano, w wielu przypadkach proces Java w końcu zgłosi wyjątek środowiska wykonawczego OOM, co jest wyraźnym wskaźnikiem wyczerpania zasobów pamięci. W takim przypadku należy odróżnić normalne wyczerpanie pamięci od wycieku. Przeanalizuj wiadomość OOM i spróbuj znaleźć winowajcę na podstawie dyskusji podanych powyżej.

Często, jeśli aplikacja Java żąda więcej pamięci niż oferuje sterta środowiska wykonawczego, może to być spowodowane złym projektem. Na przykład, jeśli aplikacja tworzy wiele kopii obrazu lub ładuje plik do tablicy, zabraknie jej miejsca na dane, gdy obraz lub plik jest bardzo duży. To normalne wyczerpanie zasobów. Aplikacja działa zgodnie z założeniami (chociaż ten projekt jest wyraźnie bezmyślny).

Ale jeśli aplikacja stale zwiększa wykorzystanie pamięci podczas przetwarzania tego samego rodzaju danych, może wystąpić przeciek pamięci.

2. Włącz pełne zbieranie śmieci

Jednym z najszybszych sposobów stwierdzenia, że ​​rzeczywiście masz przeciek pamięci, jest włączenie pełnego wyrzucania elementów bezużytecznych. Problemy z ograniczeniami pamięci można zwykle zidentyfikować, badając wzorce w danych wyjściowych verbosegc .

W szczególności argument -verbosegc umożliwia generowanie śladu za każdym razem, gdy rozpoczyna się proces wyrzucania elementów bezużytecznych (GC). Oznacza to, że gdy pamięć jest gromadzona, raporty podsumowujące są drukowane z błędem standardowym, co daje poczucie, jak zarządzana jest pamięć.

Oto kilka typowych danych wyjściowych generowanych za pomocą opcji –verbosegc :

szczegółowe wyjście do zbierania śmieci

Każdy blok (lub sekcja) w tym pliku śledzenia GC jest ponumerowany w porządku rosnącym. Aby zrozumieć ten ślad, należy spojrzeć na kolejne sekcje o niepowodzeniu alokacji i poszukać, czy zwolniona pamięć (w bajtach i procentach) maleje w czasie, podczas gdy całkowita pamięć (tutaj, 19725304) rośnie. Są to typowe oznaki wyczerpania pamięci.

3. Włącz profilowanie

Różne maszyny JVM oferują różne sposoby generowania plików śledzenia w celu odzwierciedlenia aktywności sterty, które zazwyczaj zawierają szczegółowe informacje o typie i wielkości obiektów. Nazywa się to profilowaniem sterty .

4. Przeanalizuj ślad

Ten post koncentruje się na śledzeniu generowanym przez Java VisualVM. Ślady mogą mieć różne formaty, ponieważ mogą być generowane przez różne narzędzia do wykrywania wycieków pamięci Java, ale ich idea jest zawsze taka sama: znajdź blok obiektów na stercie, których nie powinno tam być, i określ, czy te obiekty się kumulują zamiast zwalniać. Szczególnie interesujące są obiekty przejściowe, o których wiadomo, że są przydzielane za każdym razem, gdy w aplikacji Java zostanie wyzwolone określone zdarzenie. Obecność wielu instancji obiektów, które powinny istnieć tylko w niewielkich ilościach, ogólnie wskazuje na błąd aplikacji.

Wreszcie, rozwiązanie problemu wycieków pamięci wymaga dokładnego przejrzenia kodu. Poznanie rodzaju wycieku obiektu może być bardzo pomocne i znacznie przyspieszyć debugowanie.

Jak działa wyrzucanie elementów bezużytecznych w JVM?

Zanim zaczniemy naszą analizę aplikacji z problemem wycieku pamięci, przyjrzyjmy się najpierw, jak działa odśmiecanie w JVM.

JVM wykorzystuje formę garbage collectora zwanego kolektorem śledzenia , który zasadniczo działa poprzez zatrzymywanie otaczającego go świata, oznaczanie wszystkich obiektów głównych (obiekty, do których odwołują się bezpośrednio działające wątki) i podążanie za ich referencjami, oznaczając każdy obiekt, który widzi po drodze.

Java implementuje coś, co nazywa się generacyjnym garbage collectorem, opierając się na założeniu hipotezy pokoleniowej, która mówi, że większość tworzonych obiektów jest szybko odrzucana , a obiekty, które nie są szybko zbierane, prawdopodobnie będą dostępne przez jakiś czas .

Opierając się na tym założeniu, Java dzieli obiekty na wiele generacji. Oto wizualna interpretacja:

Partycje Java na wiele pokoleń

  • Młode pokolenie – tu zaczynają się obiekty. Ma dwie podgeneracje:

    • Eden Space - Tutaj zaczynają się obiekty. Większość obiektów jest tworzona i niszczona w Przestrzeni Edenu. Tutaj GC wykonuje Minor GC , które są zoptymalizowanymi zbiorami śmieci. Po wykonaniu Minor GC wszelkie odniesienia do obiektów, które są nadal potrzebne, są migrowane do jednej z przestrzeni ocalałych (S0 lub S1).

    • Survivor Space (S0 i S1) - Tutaj trafiają obiekty, które przetrwają Eden. Są dwa z nich i tylko jeden jest używany w danym momencie (chyba że mamy poważny wyciek pamięci). Jeden jest oznaczony jako pusty , a drugi jako żywy , naprzemiennie z każdym cyklem GC.

  • Tenured Generation - Znany również jako stara generacja (stara przestrzeń na ryc. 2), ta przestrzeń zawiera starsze obiekty o dłuższym okresie życia (przeniesione z przestrzeni ocalałych, jeśli żyją wystarczająco długo). Kiedy ta przestrzeń jest wypełniona, GC wykonuje Full GC , co kosztuje więcej pod względem wydajności. Jeśli ta przestrzeń rośnie bez ograniczeń, JVM wyrzuci OutOfMemoryError - Java heap space .

  • Trwałe generowanie — trzecia generacja blisko spokrewniona z generacją okresową, generacja stała jest wyjątkowa, ponieważ przechowuje dane wymagane przez maszynę wirtualną do opisania obiektów, które nie mają odpowiednika na poziomie języka Java. Na przykład obiekty opisujące klasy i metody są przechowywane w stałej generacji.

Java jest wystarczająco inteligentna, aby stosować różne metody zbierania śmieci dla każdej generacji. Młode pokolenie jest obsługiwane za pomocą śledzącego, kopiującego kolektora zwanego Parallel New Collector . Ten kolekcjoner zatrzymuje świat, ale ponieważ młode pokolenie jest generalnie małe, przerwa jest krótka.

Aby uzyskać więcej informacji o generacjach JVM i ich działaniu, odwiedź stronę Zarządzanie pamięcią w dokumentacji Java HotSpot Virtual Machine.

Wykrywanie przecieku pamięci

Aby znaleźć wycieki pamięci i je wyeliminować, potrzebujesz odpowiednich narzędzi do wycieków pamięci. Czas wykryć i usunąć taki wyciek za pomocą Java VisualVM.

Zdalne profilowanie sterty za pomocą Java VisualVM

VisualVM to narzędzie, które zapewnia wizualny interfejs do przeglądania szczegółowych informacji o uruchomionych aplikacjach opartych na technologii Java.

Dzięki VisualVM możesz przeglądać dane związane z aplikacjami lokalnymi i tymi, które działają na zdalnych hostach. Można również przechwytywać dane o instancjach oprogramowania JVM i zapisywać je w systemie lokalnym.

Aby korzystać ze wszystkich funkcji Java VisualVM, należy uruchomić platformę Java Standard Edition (Java SE) w wersji 6 lub nowszej.

Powiązane: Dlaczego musisz już uaktualnić do wersji Java 8

Włączanie połączenia zdalnego dla JVM

W środowisku produkcyjnym często trudno jest uzyskać dostęp do rzeczywistej maszyny, na której będzie działał nasz kod. Na szczęście możemy zdalnie profilować naszą aplikację Java.

Najpierw musimy przyznać sobie dostęp do JVM na maszynie docelowej. Aby to zrobić, utwórz plik o nazwie jstatd.all.policy z następującą zawartością:

 grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; };

Po utworzeniu pliku musimy włączyć zdalne połączenia z docelową maszyną wirtualną za pomocą narzędzia jstatd - Virtual Machine jstat Daemon w następujący sposób:

 jstatd -p <PORT_NUMBER> -J-Djava.security.policy=<PATH_TO_POLICY_FILE>

Na przykład:

 jstatd -p 1234 -J-Djava.security.policy=D:\jstatd.all.policy

Dzięki uruchomieniu jstatd w docelowej maszynie wirtualnej jesteśmy w stanie połączyć się z maszyną docelową i zdalnie profilować aplikację pod kątem problemów z wyciekiem pamięci.

Łączenie ze zdalnym hostem

Na komputerze klienckim otwórz monit i wpisz jvisualvm , aby otworzyć narzędzie VisualVM.

Następnie musimy dodać zdalnego hosta w VisualVM. Ponieważ docelowa maszyna JVM umożliwia zdalne połączenia z innej maszyny z J2SE 6 lub nowszym, uruchamiamy narzędzie Java VisualVM i łączymy się ze zdalnym hostem. Jeśli połączenie ze zdalnym hostem powiodło się, zobaczymy aplikacje Java uruchomione w docelowej maszynie wirtualnej JVM, jak widać tutaj:

działa w docelowym jvm

Aby uruchomić profiler pamięci w aplikacji, wystarczy dwukrotnie kliknąć jego nazwę w panelu bocznym.

Teraz, gdy wszyscy mamy już skonfigurowany analizator pamięci, zbadajmy aplikację z problemem wycieku pamięci, który nazwiemy MemLeak .

MemLeak

Oczywiście istnieje wiele sposobów tworzenia wycieków pamięci w Javie. Dla uproszczenia zdefiniujemy klasę jako klucz w HashMap , ale nie zdefiniujemy metod equals() i hashcode().

HashMap to implementacja tablicy mieszającej dla interfejsu Map i jako taka definiuje podstawowe pojęcia klucza i wartości: każda wartość jest powiązana z unikalnym kluczem, więc jeśli klucz dla danej pary klucz-wartość jest już obecny w HashMap, jego aktualna wartość zostaje zastąpiona.

Obowiązkowo nasza klasa klucza zapewnia poprawną implementację metod equals() i hashcode() . Bez nich nie ma gwarancji, że zostanie wygenerowany dobry klucz.

Nie definiując metod equals() i hashcode() , dodajemy ten sam klucz do HashMap w kółko i zamiast zastępować klucz tak, jak powinien, HashMap stale rośnie, nie identyfikując tych identycznych kluczy i wyrzucając OutOfMemoryError .

Oto klasa MemLeak:

 package com.post.memory.leak; import java.util.Map; public class MemLeak { public final String key; public MemLeak(String key) { this.key =key; } public static void main(String args[]) { try { Map map = System.getProperties(); for(;;) { map.put(new MemLeak("key"), "value"); } } catch(Exception e) { e.printStackTrace(); } } }

Uwaga: wyciek pamięci nie jest spowodowany nieskończoną pętlą w wierszu 14.: nieskończona pętla może prowadzić do wyczerpania zasobów, ale nie do wycieku pamięci. Gdybyśmy poprawnie zaimplementowali metody equals() i hashcode() , kod działałby dobrze nawet z nieskończoną pętlą, ponieważ mielibyśmy tylko jeden element wewnątrz HashMap.

(Dla zainteresowanych, oto kilka alternatywnych sposobów (celowego) generowania nieszczelności.)

Korzystanie z Java VisualVM

Dzięki Java VisualVM możemy monitorować pamięć sterty Java i identyfikować, czy jej zachowanie wskazuje na wyciek pamięci.

Oto graficzna reprezentacja analizatora Java Heap firmy MemLeak tuż po inicjalizacji (przypomnij sobie naszą dyskusję na temat różnych generacji):

monitoruj wycieki pamięci za pomocą Java Visualvm

Po zaledwie 30 sekundach stara generacja jest prawie pełna, co wskazuje, że nawet z pełnym GC, stara generacja stale rośnie, co jest wyraźną oznaką wycieku pamięci.

Jeden ze sposobów na wykrycie przyczyny tego wycieku pokazano na poniższym obrazku ( kliknij, aby powiększyć ), wygenerowanym przy użyciu Java VisualVM ze zrzutem stosu. Tutaj widzimy, że 50% obiektów Hashtable$Entry znajduje się na stercie , podczas gdy druga linia wskazuje nam klasę MemLeak . W związku z tym wyciek pamięci jest spowodowany przez tablicę skrótów używaną w klasie MemLeak .

Wyciek pamięci tablicy mieszającej

Na koniec przyjrzyj się stercie Java tuż po naszym OutOfMemoryError , w którym młode i stare pokolenia są całkowicie pełne .

Błąd brak pamięci

Wniosek

Wycieki pamięci należą do najtrudniejszych do rozwiązania problemów z aplikacjami Java, ponieważ symptomy są zróżnicowane i trudne do odtworzenia. W tym miejscu opisaliśmy krok po kroku podejście do wykrywania wycieków pamięci i identyfikowania ich źródeł. Ale przede wszystkim uważnie czytaj komunikaty o błędach i zwracaj uwagę na ślady stosu — nie wszystkie przecieki są tak proste, jak się wydaje.

dodatek

Wraz z Java VisualVM istnieje kilka innych narzędzi, które mogą wykrywać wycieki pamięci. Wiele detektorów wycieków działa na poziomie biblioteki, przechwytując wywołania procedur zarządzania pamięcią. Na przykład HPROF jest prostym narzędziem wiersza poleceń dołączonym do Java 2 Platform Standard Edition (J2SE) do profilowania sterty i procesora. Dane wyjściowe HPROF można analizować bezpośrednio lub wykorzystać jako dane wejściowe dla innych narzędzi, takich jak JHAT . Gdy pracujemy z aplikacjami Java 2 Enterprise Edition (J2EE), istnieje wiele bardziej przyjaznych rozwiązań analizujących zrzuty sterty, takich jak IBM Heapdumps for Websphere.