Czym jest profiler w Java?
Profiler to narzędzie służące do analizy działania aplikacji gdy jest ona uruchomiona. Pozwala sprawdzić, w jaki sposób program wykorzystuje zasoby komputera oraz które fragmenty kodu mogą powodować problemy z wydajnością.
W przypadku aplikacji Java profiler może analizować między innymi:
- użycie procesora CPU,
- wykorzystanie pamięci Heap,
- liczbę i rozmiar tworzonych obiektów,
- działanie Garbage Collectora,
- pracę wątków,
- blokady i deadlocki,
- czas wykonywania poszczególnych metod.
Dzięki profilerowi nie musisz zgadywać, dlaczego aplikacja działa wolno lub zużywa zbyt dużo pamięci. Zamiast tego można obserwować rzeczywiste zachowanie JVM i znaleźć konkretne miejsce, które jest źródłem problemu. Przykładowo, jeśli aplikacja zużywa duże zasoby procesora, profiler może wskazać metodę wykonywaną najczęściej. Jeśli natomiast zużycie pamięci cały czas rośnie, można sprawdzić, jakie obiekty pozostają w pamięci i czy nie zachodzi wyciek pamięci.
- Czym jest profiler w Java?
- CPU Profiling – gdzie aplikacja traci czas?
- Memory Profiling – gdzie znika pamięć?
- Memory Leak w Java
- Heap Dump – fotografia pamięci JVM
- Analiza Garbage Collectora
- Thread Profiling – analiza wątków
- Najpopularniejsze profilery Java
- Profilowanie aplikacji za pomocą VisualVM
- Dobre praktyki profilowania
- Podsumowanie
CPU Profiling – gdzie aplikacja traci czas?
CPU Profiling pozwala sprawdzić, które fragmenty aplikacji zużywają najwięcej czasu procesora. Dzięki temu można znaleźć metody, które są wykonywane zbyt często, działają zbyt długo albo powodują niepotrzebne obciążenie CPU.
Profiler pokazuje między innymi:
- które metody są wykonywane najczęściej,
- ile czasu aplikacja spędza w poszczególnych metodach,
- jakie metody wywołują inne metody,
- które fragmenty kodu są tak zwanymi hot spotami, czyli miejscami szczególnie mocno obciążającymi procesor.
W praktyce często analizuje się dwa widoki:
- Hot Spots – pokazują metody zużywające najwięcej czasu CPU,
- Call Tree – pokazuje drzewo wywołań metod, dzięki czemu można zobaczyć, skąd dana kosztowna operacja została wywołana.
CPU Profiling jest szczególnie przydatny, gdy aplikacja działa wolno mimo tego, że nie ma problemów z pamięcią. Pozwala wtedy sprawdzić, czy przyczyną nie są kosztowne obliczenia, zbyt duża liczba wywołań metod, niewydajne algorytmy albo niepotrzebne pętle.
Memory Profiling – gdzie znika pamięć?
Memory Profiling służy do analizy sposobu, w jaki aplikacja Java wykorzystuje pamięć. Pozwala sprawdzić, jakie obiekty są tworzone, ile pamięci zajmują oraz czy są poprawnie usuwane przez Garbage Collector.
Profiler pamięci może pokazać między innymi:
- liczbę instancji poszczególnych klas,
- ilość pamięci zajmowanej przez konkretne obiekty,
- miejsca w kodzie, w których powstaje najwięcej nowych obiektów,
- obiekty, które pozostają w pamięci przez długi czas,
- zmiany wykorzystania Heap w trakcie działania aplikacji.
Przykładowo, jeśli aplikacja tworzy bardzo dużą liczbę obiektów tymczasowych, może to prowadzić do częstszego uruchamiania Garbage Collectora. Sama aplikacja może działać poprawnie, ale dodatkowa praca wykonywana przez GC może negatywnie wpływać na jej wydajność.
Memory Profiling jest szczególnie przydatny wtedy, gdy wykorzystanie pamięci stopniowo rośnie i nie wraca do wcześniejszego poziomu nawet po wykonaniu Garbage Collectora. Może to wskazywać na sytuację, w której część obiektów pozostaje osiągalna, mimo że aplikacja już ich nie potrzebuje.
Warto jednak pamiętać, że duża liczba obiektów sama w sobie nie oznacza problemu. Kluczowe jest sprawdzenie, jakie obiekty zajmują pamięć, jak długo pozostają w Heap oraz czy ich liczba stale rośnie.
W takich sytuacjach profilowanie pamięci pozwala przejść od ogólnego problemu typu „aplikacja zużywa za dużo RAM-u” do konkretnych klas i fragmentów kodu odpowiedzialnych za zużycie pamięci.
Memory Leak w Java
Java posiada Garbage Collector, który automatycznie usuwa z pamięci obiekty, do których aplikacja nie posiada już referencji. Nie oznacza to jednak, że w aplikacji Java nie mogą występować wycieki pamięci.
Memory Leak pojawia się wtedy, gdy aplikacja nadal przechowuje referencje do obiektów, których już nie potrzebuje. Z punktu widzenia Garbage Collectora takie obiekty są nadal używane, dlatego nie mogą zostać usunięte z pamięci.
Memory Leak może wystąpić między innymi przez:
- nieograniczone cache,
- kolekcje, z których obiekty nigdy nie są usuwane,
- statyczne referencje,
- niepoprawnie usuwane listenery,
- źle zarządzane zasoby i obiekty długowieczne.
W przypadku przekroczeniu stosu zostanie wyrzucony błąd:
java.lang.OutOfMemoryError: Java heap space
Heap Dump – fotografia pamięci JVM
Heap Dump to zrzut zawartości pamięci Heap w konkretnym momencie działania aplikacji. Można go potraktować jak „fotografię” pamięci JVM, która pokazuje, jakie obiekty aktualnie znajdują się w pamięci oraz jakie referencje między nimi istnieją.
Heap Dump pozwala sprawdzić między innymi:
- jakie klasy posiadają najwięcej instancji,
- które obiekty zajmują najwięcej pamięci,
- jakie obiekty są nadal osiągalne,
- co przechowuje referencje do obiektów, które powinny zostać usunięte,
- które kolekcje lub cache nadmiernie się rozrastają.
Analiza Garbage Collectora
Profiler może być również wykorzystany do obserwowania pracy Garbage Collectora. Pozwala sprawdzić, jak często GC się uruchamia, ile pamięci odzyskuje oraz czy jego działanie nie wpływa negatywnie na wydajność aplikacji.
Podczas analizy Garbage Collectora warto zwrócić uwagę między innymi na:
- częstotliwość uruchamiania GC,
- czas trwania poszczególnych cykli,
- wykorzystanie pamięci Heap przed i po GC,
- liczbę alokowanych obiektów,
- występowanie długich pauz aplikacji.
Typowy wykres użycia pamięci często przypomina zęby piły. Aplikacja stopniowo zajmuje coraz więcej pamięci, następnie uruchamia się Garbage Collector i część pamięci zostaje zwolniona.
Jeżeli jednak GC uruchamia się bardzo często albo odzyskuje niewielką ilość pamięci, może to oznaczać problem. Przyczyną może być na przykład zbyt duża liczba tworzonych obiektów, za mały Heap albo wyciek pamięci.
Szczególnie niepokojąca jest sytuacja, w której aplikacja spędza coraz więcej czasu na pracy Garbage Collectora, a mimo to ilość wolnej pamięci pozostaje niewielka. Może to prowadzić do znaczącego spadku wydajności, a ostatecznie nawet do błędu OutOfMemoryError.
Analiza GC nie polega więc tylko na sprawdzaniu, czy Garbage Collector działa. Ważniejsze jest określenie, czy działa wystarczająco efektywnie i czy nie staje się jednym z głównych źródeł opóźnień w aplikacji.
Thread Profiling – analiza wątków
Thread Profiling służy do analizy pracy wątków działających w aplikacji. Jest szczególnie przydatny wtedy, gdy aplikacja przestaje odpowiadać, część operacji wykonuje się bardzo wolno albo podejrzewamy problemy z synchronizacją.
Profiler pozwala sprawdzić między innymi:
- jakie wątki aktualnie działają,
- w jakim stanie się znajdują,
- które wątki są zablokowane,
- na co oczekują,
- czy występują deadlocki,
- które fragmenty kodu powodują długie oczekiwanie.
Wątki w JVM mogą znajdować się w różnych stanach, na przykład:
- RUNNABLE – wątek jest gotowy do wykonania lub aktualnie wykonuje kod,
- WAITING – czeka bez określonego limitu czasu,
- TIMED_WAITING – czeka przez określony czas,
- BLOCKED – oczekuje na możliwość wejścia do zsynchronizowanej sekcji kodu.
Szczególnie problematyczny jest stan BLOCKED, jeśli wiele wątków czeka na ten sam lock. Może to prowadzić do spadku wydajności całej aplikacji.
Przykładowo:
public synchronized void process() {
// długa operacja
}Jeżeli wiele wątków jednocześnie wywołuje metodę process(), tylko jeden z nich może wykonywać ją w danym momencie. Pozostałe będą oczekiwać na zwolnienie blokady.
Jeszcze poważniejszym problemem jest deadlock, czyli sytuacja, w której dwa lub więcej wątków czeka wzajemnie na zasoby posiadane przez inne wątki. W takim przypadku żaden z nich nie może kontynuować pracy.
Do analizy problemów z wątkami przydaje się również Thread Dump, czyli zrzut aktualnego stanu wszystkich wątków JVM. Pozwala on sprawdzić stos wywołań każdego wątku i znaleźć miejsce, w którym został on zablokowany lub zatrzymany.
Thread Profiling jest więc szczególnie przydatny przy diagnozowaniu zawieszających się aplikacji, problemów z synchronizacją oraz nadmiernego oczekiwania pomiędzy wątkami.
Najpopularniejsze profilery Java
- VisualVM – darmowe narzędzie do monitorowania i profilowania aplikacji Java. Pozwala analizować CPU, pamięć, wątki oraz wykonywać Heap Dump i Thread Dump.
- Java Flight Recorder + JDK Mission Control – narzędzia dostarczane w ekosystemie JDK, szczególnie przydatne do zbierania danych diagnostycznych przy stosunkowo niewielkim narzucie.
- IntelliJ Profiler – profiler zintegrowany z IntelliJ IDEA, wygodny podczas lokalnej analizy aplikacji bezpośrednio z poziomu IDE.
- JProfiler – rozbudowane komercyjne narzędzie do analizy CPU, pamięci, wątków, baz danych i wielu innych elementów aplikacji.
- YourKit Java Profiler – komercyjny profiler oferujący zaawansowaną analizę wydajności aplikacji Java.
Profilowanie aplikacji za pomocą VisualVM
W dalszej części skupię sięna opisaniu bardzo popularnego, darmowego profilera o nazwie VisualVM.
Skąd pobrać VisualVM?
VisualVM jest obecnie dostępny jako osobne, darmowe narzędzie i można go pobrać z oficjalnej strony projektu:
Po pobraniu archiwum ZIP wystarczy je rozpakować i uruchomić odpowiedni plik:
visualvm\bin\visualvm.exe
Monitor
akładka Monitor daje szybki podgląd ogólnego stanu aplikacji.
Można zobaczyć między innymi:
- wykorzystanie CPU,
- wykorzystanie Heap,
- Metaspace,
- liczbę załadowanych klas,
- liczbę działających wątków,
- aktywność Garbage Collectora.
Jest to dobre miejsce na rozpoczęcie analizy. Jeżeli na przykład wykorzystanie Heap cały czas rośnie, można później przejść do dokładniejszej analizy pamięci.

Threads
Zakładka Threads pozwala obserwować działające w aplikacji wątki oraz ich stan w czasie.
VisualVM przedstawia aktywność wątków na osi czasu i rozróżnia kilka rodzajów stanów:
- Running – wątek jest aktywny i może wykonywać kod,
- Sleeping – wątek został czasowo uśpiony,
- Wait – wątek oczekuje na określone zdarzenie,
- Park – wątek został zaparkowany i oczekuje na wznowienie,
- Monitor – wątek oczekuje na uzyskanie dostępu do monitora, np. do sekcji
synchronized.
Dzięki temu można szybko zauważyć, że określony wątek przez większość czasu oczekuje, jest blokowany albo intensywnie pracuje.
VisualVM umożliwia również wykonanie Thread Dump, który pokazuje stos wywołań wszystkich wątków w konkretnym momencie. Jest to szczególnie przydatne podczas diagnozowania deadlocków oraz proble

Sampler i Profiler
Najważniejsze z punktu widzenia profilowania są zakładki pozwalające analizować CPU oraz pamięć.
W przypadku CPU można sprawdzić, które metody są wykonywane najczęściej i gdzie aplikacja spędza najwięcej czasu.
Przy analizie pamięci można natomiast zobaczyć między innymi liczbę instancji poszczególnych klas oraz ilość zajmowanej przez nie pamięci.
Dzięki temu można szybko przejść od ogólnego problemu:
aplikacja zużywa dużo CPU
do bardziej konkretnej informacji:
większość czasu procesora jest zużywana przez konkretną metodę.
Podobnie w przypadku pamięci można znaleźć klasy, których liczba instancji stale rośnie.

Dobre praktyki profilowania
Podczas profilowania warto pamiętać o kilku zasadach:
- najpierw zidentyfikuj problem – sprawdź, czy dotyczy CPU, pamięci, Garbage Collectora czy wątków,
- profiluj realistyczne scenariusze – najlepiej takie, które odpowiadają rzeczywistemu użyciu aplikacji,
- nie optymalizuj na podstawie pojedynczego pomiaru – wyniki warto potwierdzić kilkukrotnie,
- porównuj wyniki przed i po zmianie – dopiero wtedy można ocenić, czy optymalizacja rzeczywiście pomogła,
- uważaj na narzut profilera – niektóre sposoby profilowania mogą wpływać na wydajność aplikacji,
Podsumowanie
Profilery pozwalają zajrzeć do wnętrza działającej aplikacji Java i sprawdzić, co faktycznie dzieje się podczas jej pracy. Dzięki nim można analizować wykorzystanie CPU, pamięci, działanie Garbage Collectora oraz pracę wątków.
Są szczególnie przydatne przy szukaniu problemów takich jak Memory Leak, wysokie użycie procesora, częste uruchamianie GC, deadlocki czy długie czasy wykonywania metod.
Do dyspozycji mamy wiele narzędzi, między innymi VisualVM, Java Flight Recorder, JDK Mission Control, JProfiler czy YourKit. Na początek bardzo dobrze sprawdza się VisualVM, ponieważ jest darmowy, prosty w obsłudze i pozwala szybko przeanalizować najważniejsze elementy działania aplikacji.
Najważniejsze jest jednak to, aby nie optymalizować kodu na podstawie przypuszczeń. Najpierw warto zebrać dane, znaleźć rzeczywiste wąskie gardło, a dopiero później wprowadzać zmiany.
