Jak znaleźć wyciek pamięci Java

142

Jak znaleźć wyciek pamięci w Javie (używając na przykład JHat)? Próbowałem załadować zrzut sterty do JHat, aby przyjrzeć się podstawowym. Jednak nie rozumiem, jak mam być w stanie znaleźć odniesienie źródłowe ( ref ) lub jakkolwiek to się nazywa. Zasadniczo mogę powiedzieć, że istnieje kilkaset megabajtów wpisów tablicy skrótów ([java.util.HashMap $ Entry lub coś w tym rodzaju), ale mapy są używane w każdym miejscu ... Czy istnieje sposób na wyszukiwanie dużych map , a może znaleźć ogólne korzenie dużych drzew obiektów?

[Edytuj] Ok, przeczytałem dotychczas odpowiedzi, ale powiedzmy, że jestem tanim draniem (co oznacza, że ​​jestem bardziej zainteresowany nauką korzystania z JHat niż płaceniem za JProfiler). Ponadto JHat jest zawsze dostępny, ponieważ jest częścią JDK. Chyba że, oczywiście, z JHat nie ma innego wyjścia, jak tylko brutalna siła, ale nie mogę uwierzyć, że tak jest.

Nie sądzę też, żebym mógł faktycznie modyfikować (dodając rejestrowanie wszystkich rozmiarów map) i uruchamiać go na tyle długo, bym zauważył wyciek.

jwiklund
źródło
To kolejny „głos” na JProfiler. Działa całkiem dobrze do analizy sterty, ma przyzwoity interfejs użytkownika i działa całkiem dobrze. Jak mówi McKenzieG1, 500 dolarów jest tańsze niż czas, który w innym przypadku musiałbyś poświęcić na szukanie źródła tych wycieków. Jeśli chodzi o cenę narzędzi, to nie jest źle.
joev
Oracle ma odpowiednią stronę tutaj: docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/…
Laurel

Odpowiedzi:

126

Używam następującego podejścia do znajdowania wycieków pamięci w Javie. Użyłem jProfilera z dużym sukcesem, ale wierzę, że każde specjalistyczne narzędzie z możliwościami graficznymi (różnice są łatwiejsze do analizy w formie graficznej) będzie działać.

  1. Uruchom aplikację i poczekaj, aż osiągnie stan „stabilny”, po zakończeniu całej inicjalizacji i bezczynności aplikacji.
  2. Uruchom kilkakrotnie operację podejrzaną o wyciek pamięci, aby umożliwić dowolną inicjalizację pamięci podręcznej związaną z bazą danych.
  3. Uruchom GC i zrób migawkę pamięci.
  4. Uruchom operację ponownie. W zależności od złożoności operacji i rozmiarów danych, które są przetwarzane, operacja może wymagać uruchomienia kilka lub wiele razy.
  5. Uruchom GC i zrób migawkę pamięci.
  6. Uruchom różnicę dla 2 migawek i przeanalizuj ją.

Zasadniczo analiza powinna rozpocząć się od największej pozytywnej różnicy, powiedzmy, typów obiektów i znaleźć przyczynę, dla której te dodatkowe obiekty pozostają w pamięci.

W przypadku aplikacji internetowych, które przetwarzają żądania w kilku wątkach, analiza staje się bardziej skomplikowana, ale nadal obowiązuje podejście ogólne.

Zrobiłem wiele projektów, które miały na celu zmniejszenie zużycia pamięci przez aplikacje i to ogólne podejście z pewnymi poprawkami i sztuczkami specyficznymi dla aplikacji zawsze działało dobrze.

Dima Malenko
źródło
7
Większość (jeśli nie wszystkie) programów do profilowania języka Java zapewnia możliwość wywołania GC jednym kliknięciem przycisku. Lub możesz wywołać System.gc () z odpowiedniego miejsca w kodzie.
Dima Malenko
3
Nawet jeśli wywołasz System.gc (), JVM może zdecydować się na zaniedbanie wywołania. AFAIK jest to specyficzne dla JVM. +1 do odpowiedzi.
Aniket Thakur
4
Czym dokładnie jest „migawka pamięci”? Czy jest coś, co powie mi numer każdego typu obiektu, który działa w moim kodzie?
gnomed
2
Jak przejść od „zacznij od największej dodatniej różnicy według typów obiektów” do „znalezienia powodu, dla którego te dodatkowe obiekty pozostają w pamięci”? Widzę bardzo ogólne rzeczy, takie jak int [], Object [], String itp. Jak mogę sprawdzić, skąd pochodzą?
Vituel
48

Odpowiadający tutaj, muszę powiedzieć, że uzyskanie narzędzia, które nie zajmuje 5 minut, aby odpowiedzieć na każde kliknięcie, znacznie ułatwia znalezienie potencjalnych wycieków pamięci.

Ponieważ ludzie sugerują kilka narzędzi (próbowałem tylko wizualnego wm, ponieważ dostałem go w wersji próbnej JDK i JProbe), pomyślałem, że powinienem zasugerować darmowe / otwarte narzędzie zbudowane na platformie Eclipse, Memory Analyzer (czasami określane jako pamięć SAP analizator) dostępny na http://www.eclipse.org/mat/ .

To, co jest naprawdę fajne w tym narzędziu, to to, że zindeksowało zrzut sterty, kiedy go po raz pierwszy otworzyłem, co pozwoliło mu pokazać dane takie jak zachowana sterta bez czekania 5 minut na każdy obiekt (prawie wszystkie operacje były tony szybsze niż inne narzędzia, które wypróbowałem) .

Po otwarciu zrzutu pierwszy ekran pokazuje wykres kołowy z największymi obiektami (licząc zachowaną stertę) i można szybko przejść do obiektów, które są zbyt duże dla wygody. Ma również opcję Znajdź prawdopodobnie podejrzanych o wyciek, która może się przydać, ale ponieważ nawigacja była dla mnie wystarczająca, tak naprawdę nie wdałem się w to.

jwiklund
źródło
1
Warto zauważyć: najwyraźniej w Javie 5 i nowszych HeapDumpOnCtrlBreakparametrach VM nie jest dostępny . Rozwiązaniem, które znalazłem (do tej pory wciąż szukam) jest użycie JMap do zrzucenia .hprofpliku, który następnie umieściłem w Eclipse i używam MAT do zbadania.
Ben,
1
Jeśli chodzi o uzyskiwanie zrzutu sterty, większość profilerów (w tym JVisualVM) zawiera opcję zrzutu zarówno sterty, jak i wątków do pliku.
bbaja42
13

Narzędzie to duża pomoc.

Jednak są chwile, kiedy nie możesz użyć narzędzia: zrzut sterty jest tak duży, że powoduje awarię narzędzia, próbujesz rozwiązać problem z maszyną w jakimś środowisku produkcyjnym, do którego masz tylko dostęp do powłoki itp.

W takim przypadku dobrze jest wiedzieć, jak obejść plik zrzutu hprof.

Szukaj STRON POCZĄTKOWYCH. To pokazuje, które obiekty używają najwięcej pamięci. Ale obiekty nie są grupowane razem wyłącznie według typu: każdy wpis zawiera również identyfikator „śledzenia”. Następnie możesz wyszukać to „TRACE nnnn”, aby zobaczyć kilka górnych ramek na stosie, w których obiekt został przydzielony. Często, gdy widzę, gdzie obiekt jest przydzielony, znajduję błąd i gotowe. Zwróć również uwagę, że możesz kontrolować liczbę klatek zapisywanych w stosie za pomocą opcji -Xrunhprof.

Jeśli sprawdzasz witrynę alokacji i nie widzisz nic złego, musisz rozpocząć łączenie wsteczne od niektórych z tych aktywnych obiektów do obiektów głównych, aby znaleźć nieoczekiwany łańcuch referencyjny. W tym przypadku narzędzie naprawdę pomaga, ale możesz zrobić to samo ręcznie (cóż, za pomocą grep). Nie istnieje tylko jeden obiekt główny (tj. Obiekt nie podlega czyszczeniu). Wątki, klasy i ramki stosu działają jak obiekty główne, a wszystko, do czego silnie się odwołują, nie jest kolekcjonowane.

Aby to zrobić, poszukaj w sekcji HEAP DUMP wpisów o złym identyfikatorze śledzenia. Spowoduje to przejście do wpisu OBJ lub ARR, który zawiera unikalny identyfikator obiektu w postaci szesnastkowej. Wyszukaj wszystkie wystąpienia tego identyfikatora, aby dowiedzieć się, kto ma silne odniesienie do obiektu. Podążaj każdą z tych ścieżek do tyłu, gdy rozgałęziają się, aż dowiesz się, gdzie jest wyciek. Zobacz, dlaczego narzędzie jest tak przydatne?

Statyczne elementy członkowskie są wielokrotnym sprawcą wycieków pamięci. W rzeczywistości nawet bez narzędzia warto poświęcić kilka minut na przejrzenie kodu w poszukiwaniu statycznych członków mapy. Czy mapa może się powiększyć? Czy cokolwiek czyści swoje wpisy?

erickson
źródło
„Zrzut sterty jest tak ogromny, że powoduje awarię narzędzia” - ostatnio sprawdziłem jhati MATnajwyraźniej próbuję załadować cały zrzut sterty do pamięci, więc zazwyczaj dochodzi do awarii przy OutOfMemoryErrordużych zrzutach (tj. Z aplikacji, które najbardziej potrzebowały analizy sterty! ). Wydaje się, że NetBeans Profiler używa innego algorytmu do indeksowania odwołań, które mogą działać wolno przy dużych zrzutach, ale przynajmniej nie zużywa nieograniczonej pamięci w narzędziu i powoduje awarię.
Jesse Glick,
10

W większości przypadków w aplikacjach korporacyjnych podana sterta Java jest większa niż idealny rozmiar, wynoszący maksymalnie 12 do 16 GB. Trudno mi było sprawić, by profiler NetBeans działał bezpośrednio w tych dużych aplikacjach Java.

Ale zwykle nie jest to potrzebne. Możesz użyć narzędzia jmap, które jest dostarczane z jdk, aby wykonać "na żywo" zrzut sterty, czyli jmap zrzuci stertę po uruchomieniu GC. Wykonaj jakąś operację na aplikacji, poczekaj, aż operacja zostanie zakończona, a następnie wykonaj kolejny zrzut sterty „na żywo”. Użyj narzędzi takich jak Eclipse MAT, aby załadować zrzuty sterty, posortować na histogramie, zobaczyć, które obiekty wzrosły lub które są najwyższe, to dałoby wskazówkę.

su  proceeuser
/bin/jmap -dump:live,format=b,file=/tmp/2930javaheap.hrpof 2930(pid of process)

Jest tylko jeden problem z tym podejściem; Ogromne zrzuty sterty, nawet z opcją na żywo, mogą być zbyt duże, aby można je było przenieść na okrążenie programistyczne i mogą wymagać maszyny z wystarczającą ilością pamięci / RAM do otwarcia.

W tym miejscu pojawia się histogram klas. Możesz zrzucić na żywo histogram klas za pomocą narzędzia jmap. Daje to tylko histogram klasy wykorzystania pamięci, ale w zasadzie nie będzie zawierał informacji do łańcucha referencji. Na przykład może umieścić tablicę char na górze. A gdzieś poniżej klasa String. Musisz sam narysować połączenie.

jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt

Zamiast pobierać dwa zrzuty sterty, weź dwa histogramy klas, jak opisano powyżej; Następnie porównaj histogramy klas i zobacz klasy, które rosną. Sprawdź, czy możesz powiązać klasy Java z klasami aplikacji. To da całkiem niezłą wskazówkę. Oto skrypt w Pythonie, który pomoże ci porównać dwa zrzuty histogramu jmap. histogramparser.py

Wreszcie narzędzia, takie jak JConolse i VisualVm, są niezbędne, aby zobaczyć wzrost pamięci w czasie i sprawdzić, czy występuje przeciek pamięci. Wreszcie czasami problemem może nie być wyciek pamięci, ale wysokie zużycie pamięci. W tym celu włącz rejestrowanie GC; użyj bardziej zaawansowanego i nowego GC kompaktowania, takiego jak G1GC; i możesz użyć narzędzi jdk, takich jak jstat, aby zobaczyć zachowanie GC na żywo

jstat -gccause pid <optional time interval>

Inne odniesienia do Google dla -jhat, jmap, Full GC, ogromna alokacja, G1GC

Alex Punnen
źródło
1
dodał post na blogu ze szczegółami tutaj - alexpunnen.blogspot.in/2015/06/…
Alex Punnen
5

Istnieją narzędzia, które powinny pomóc Ci znaleźć wyciek, takie jak JProbe, YourKit, AD4J lub JRockit Mission Control. Ta ostatnia to ta, którą osobiście znam najlepiej. Każde dobre narzędzie powinno pozwolić ci przejść do poziomu, na którym można łatwo zidentyfikować wycieki i gdzie są one przydzielone.

Używanie HashTables, Hashmaps lub podobnych jest jednym z niewielu sposobów, w jakie można w ogóle wyciekać pamięć w Javie. Gdybym miał ręcznie znaleźć wyciek, drukowałbym regularnie rozmiar moich HashMaps, a stamtąd znajdowałbym ten, w którym dodaję elementy i zapominam je usunąć.

Tnilsson
źródło
4

Cóż, zawsze istnieje proste rozwiązanie technologiczne polegające na dodawaniu rejestrowania rozmiaru map podczas ich modyfikacji, a następnie przeszukiwaniu dzienników, w których mapy rosną poza rozsądny rozmiar.

Mike Stone
źródło
1

NetBeans ma wbudowany profiler.

wbkang
źródło
0

Naprawdę musisz użyć profilera pamięci, który śledzi alokacje. Spójrz na JProfiler - ich funkcja „heap walker” jest świetna i mają integrację ze wszystkimi głównymi środowiskami Java IDE. To nie jest darmowe, ale też nie jest takie drogie (499 USD za jedną licencję) - szybko spalisz 500 USD, starając się znaleźć wyciek za pomocą mniej wyrafinowanych narzędzi.

McKenzieG1
źródło
0

Możesz się tego dowiedzieć, mierząc rozmiar użycia pamięci po wielokrotnym wywołaniu modułu odśmiecania pamięci:

Runtime runtime = Runtime.getRuntime();

while(true) {
    ...
    if(System.currentTimeMillis() % 4000 == 0){
        System.gc();
        float usage = (float) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
        System.out.println("Used memory: " + usage + "Mb");
    }

}

Jeśli liczby wyjściowe były równe, nie ma przecieku pamięci w aplikacji, ale jeśli zauważyłeś różnicę między liczbami użycia pamięci (rosnącymi liczbami), w projekcie występuje przeciek pamięci. Na przykład:

Used memory: 14.603279Mb
Used memory: 14.737213Mb
Used memory: 14.772224Mb
Used memory: 14.802681Mb
Used memory: 14.840599Mb
Used memory: 14.900841Mb
Used memory: 14.942261Mb
Used memory: 14.976143Mb

Zwróć uwagę, że czasami zwolnienie pamięci przez niektóre czynności, takie jak strumienie i gniazda, zajmuje trochę czasu. Nie powinieneś oceniać na podstawie pierwszych wyników, powinieneś przetestować to w określonym czasie.

Amir Fo
źródło
0

Sprawdź tę obsadę ekranuSprawdź dotyczący znajdowania wycieków pamięci za pomocą JProfiler. To wizualne wyjaśnienie odpowiedzi @Dima Malenko.

Uwaga: chociaż JProfiler nie jest oprogramowaniem darmowym, ale wersja próbna radzi sobie w obecnej sytuacji.

Vaibhav Jain
źródło
0

Ponieważ większość z nas używa już Eclipse do pisania kodu, dlaczego nie skorzystać z narzędzia Memory Analyzer Tool (MAT) w Eclipse. Działa świetnie.

Eclipse MAT to zestaw wtyczek dla Eclipse IDE, która dostarcza narzędzi do analizy heap dumpsz aplikacji Java oraz określenie memory problemswe wniosku.

Pomaga to programiście znaleźć wycieki pamięci dzięki następującym funkcjom

  1. Pozyskiwanie migawki pamięci (zrzut sterty)
  2. Histogram
  3. Zachowany stos
  4. Drzewo Dominator
  5. Exploring Paths to the GC Roots
  6. Inspektor
  7. Powszechne anty-wzorce pamięci
  8. Język zapytań o obiekt

wprowadź opis obrazu tutaj

Sreeram Nair
źródło