Warsztat z Profilowania aplikacji JVM prowadzili Kuba Kubryński i Michał Warecki. Na początku było strasznie dużo teorii (właściwie do przerwy obiadowej), no ale nie każdy ma możliwość zmierzenia się z takimi problemami na co dzień, więc należało dużo wytłumaczyć i pokazać, co jest standardowo dostępne wraz z zainstalowanym systemem operacyjnym i JDK.
Zaczęło się od pamięci w Javie. Generalnie można ją podzielić na 3 części:
- Young Generation
- Old Generation
- Permanent Generation
Młoda i stara generacja są przechowywane na stercie, a ostatnia trzecia część pamięci to Perm Gen, gdzie znajdują się załadowane klasy wraz z metodami. Co ważne, to to, że internowane Stringi lądują w Perm Gen i dlatego warto sprawdzać, ile one zajmują. Na szczęście od wersji 7 problem ten znika, gdyż teksty lądują na stercie. Więcej na ten temat tutaj: Java SE 7 Features and Enhancements.
Jest jeszcze coś takiego jak Code Cache, czyli miejsce w pamięci na skompilowany kod natywny naszych aplikacji. Pewnie jeszcze by się coś tam znalazło, ale nie jest to dla nas w tym momencie interesujące.
Następnie było o tym, jaka jest różnica między boolean a Boolean, a właściwie ile to obydwa twory zajmują pamięci. I tak boolean to jeden bit, ale Boolean to 12 bajtów nagłówka obiektu i jeden bit. A że nie można takich dziwnych, nieokrągłych wartości w pamięci używać, klasa zostanie dodatkowo wyrównana do 32 bitów (4ch bajtów) lub ich wielokrotności, w przypadku 32 bitowej maszyny wirtualnej. W przypadku 64 bitowej wersji będzie to zaokrąglenie do 64 bitów (8 bajtów), lub wielokrotności oczywiście.
Tak więc Boolean zajmuje w Javie aż 16 bajtów! Dlatego ze względów wydajnościowych, gdy trzecia wartość wyrażenia logicznego (null) jest nam niepotrzebna, to warto Boolean’a unikać.
Było jeszcze o rozmiarach, jakie zajmują obiekty:
- shallow – to rozmiar samego obiektu wraz nagłówkiem
- deep - rozmiar całego grafu obiektów, jaki jest osiągany z danego obiektu.
- retained - rozmiar definiujący, ile pamięci było by zwolnione, gdyby właśnie wystartował Garbage Collector.
Jeszcze ciekawsze rozwiązanie to odśmiecanie przez kopiowanie, czyli Copying GC. W Young Generation mamy dwa obszary: From (Survivor1) i To (Survivor2). Gdy pierwszy z nich się zapełni i gdy nadejdzie czas na odśmiecanie, to żyjące obiekty zostaną przekopiowane z From do To. Następnie zamienią się role bloków, tzn. blok From stanie się nowym blokiem To, a obecnie zapełniony To stanie się nowym From. Każde takie kopiowanie pomiędzy Survivor1 i Survivor2 zwiększa wiek życia obiektów. Dzięki temu zabiegowi kopiowania do nowej przestrzeni, pamięć jest regularnie defragmentowana. Intensywnie z tego podejścia korzysta The Garbage First Garbage Collector (G1 GC). W tym przypadku pamięć jest podzielona na sporo kawałków, pomiędzy którymi, podczas odśmiecania, są kopiowane elementy.
W JVM są dostępne różne implementacjie Garbage Collectorów. I tak przykładowo domyślnym jest Serial GC (-XX:+UseSerialGC). Działa on jednowątkowo i blokuje inne zdania. Ale przynajmniej nie traci się tutaj czasu na przełączanie kontekstu.
Kolejną implementacją jest Parallel GC (albo jak podaje dokumentacja: throughput collector). Można ją uaktywnić pomocą -XX:+UseParallelGC a cechuje się ona tym, że w przeciwieństwie do Serial GC, używa wielu wątków do przyspieszenia odśmiecania. Domyślnie tylko generacja Young (Minor) jest odśmiecana równolegle. Aby również starą (Old, Major) generację równolegle odśmiecić, należy skorzystać z opcji -XX:+UseParallelOldGC. Te strategie redukują narzut Garbage Collektora na aplikację.
Kolejną implementacją czyszczenia pamięci z nieużytków jest Concurrent Collector (-XX:+UseConcMarkSweepGC), zwany też często CMS. Jest on zalecany tam, gdzie preferuje się krótkie przerwy na odśmiecanie, gdzie jest dużo długożyjących danych, na maszynach z dwoma lub więcej procesorami.
Jest jeszcze G1 GC, oficjalnie dostępny od JDK 7. Dzieli on całą pamięć na bloki, w których są umieszczane nasze dane. Gdy obszar się zapełni, dane są przenoszone do innych bloków, a co jest nie potrzebne - jest usuwane. Docelowo G1 GC ma zastąpić CMS GC.
Następnie były przedstawione Unixowe narzędzia, które mogą nam pomóc podejść do problemu od trochę innej strony (nie koniecznie od dupy strony). I tak za pomocą htop można podejrzeć drzewo procesów, również Javy. Iostat pokazuje utylizację zasobów na poziomie I/O. Dzięki temu można się np. dowiedzieć, że dysk nam nie wyrabia. Free pokazuje ilość wolnej / zajętej pamięci, a dstat przerywania, Contex Switche i inne różne zużycia. Natomiast perf to taki systemowy profiler. Uwzględnia on Contex Switch’e, migracje CPU (przerzucenie wykonywania czegoś na inny procek), ilość instrukcji na cykl CPU, odwołania do cache procesora.
Następnie było o narzędziach dostępnych w JDK. Poniżej opiszę tylko to, czego nie opisałem w relacji z JDD 2012.
Jinfo najczęściej pokazuje komunikat „Error attaching to process...”, ale jeden z prelegentów ma na to 2 obejścia: sudo sh -c "echo 0 > /proc/sys/kernel/yama/ptrace_scope"
Lub w: /etc/sysctl.d/10-pthrace.conf ustawić kernel.yama.ptrace_scope = 0
Pierwsze jest tymczasowe i każdy admin powinien nam je na chwilę dać, a drugie jest permanentne, ale z jakiś tam względów niebezpieczne. Kolejne narzędzia to:
jstat -class PID 5s - komenda ta zrzuca nam co 5 sekund informację, ile jest załadowanych klas, ile pamięci zajmują i ile jest „odładowanych” klas
jstat -gc PID 5s - daje nam informacje o utylizacji pamięci
jstat -printcompilation PID 5s – pokazuje nam, które metody są skompilowane do kodu natywnego przez kompilator JIT (just-in-time compilation).
jstack PID - pokazuje stacktrace’y wszystkich wątków
Gdy chcemy się pobawić trochę linią poleceń, to możemy w ten sposób: java -XX:+PrintFlagsFinal -server –version wyświetlić sobie wszystkie flagi w Javie i poznać ich wartości domyślne. Niby znaczenie tych flag miało być gdzieś w dokumentacji opisane, ale jedyne co sensownego znalazłem to ten wpis na blogu: Hotspot JVM Options - The complete reference.
Później było jeszcze o Alekseyu Shipilevie i jego projekcie Java Object Layout. Niestety obecnie jest on niedostępny. Udało mi się gdzieś binarkę znaleźć, ale problem jest, jak to podpiąć pod Ideę / Eclipse’a, gdyż wszyscy linkują do strony autora, a ta już nie istnieje. Ale generalnie dzięki temu narzędziowi można podejrzeć wielkości, jakie zajmują w pamięci nasze klasy.
Było jeszcze trochę o Java VisualVM dostępnym standardowo w JDK. Warto do niego doinstalować pluginy, takie jak: MBeans, GC Viewer, Visual GC, Tracer i inne. Dzięki temu będziemy mogli sporo ciekawszych informacji podczas tuningowania naszych aplikacji uzyskać.
Po przerwie było jeszcze o Java Mission Control narzędziu, które jest dostępne JDK od wersji 7u40. Na pewno warto się przyjrzeć bliżej temu narzędziu.
Na koniec było jeszcze trochę o JProfileru i nadszedł czas na trochę praktyki. Za pomocą aplikacji profiling można było zobaczyć jak się zachowują różne implementacje Garbage Collectora, jak wyglądają wycieki pamięci itp. Podpinaliśmy się pod aplikację za pomocą JVisualVM’a i tam było bardzo ładnie widać co jak działa.
Kolejnym warsztatem, w którym uczestniczyłem, była Akka, prowadzona przez Maćka Próchniaka. Było już trochę widać zmęczenie po twarzach uczestników jak i samego prelegenta. No ale cóż, 17 godzina to trochę późna pora, zwłaszcza jak się chwilę wcześniej w innych warsztatach uczestniczyło.
Początkowy wstęp teoretyczny był bardzo klarownie przedstawiony i wszystko wydawało się bardzo jasne i proste. Takie się nie okazało, gdy przeszliśmy do kodu. Tam już było trochę ciężej. Postanowiliśmy, że zamiast osobno rozwiązywać zadania przygotowane przez Maćka, to rozwiąże on je sam, a reszta popatrzy. Tutaj jednak już nic więcej nie napiszę co zapamiętałem, ani czego się nauczyłem, gdyż trochę nie nadążałem / nie funkcjonowałem, a nieużywanie Skali od czasów ostatniego kursu Oderskiego dało mi się we znaki. Na szczęście niedługo startuje kolejna część tego kursu: Principles of Reactive Programming. Trzeba sobie do tego czasu trochę odświeżyć Scalę, aby gładko przez kurs przebrnąć.
Na koniec udało nam się zestawić razem rozproszonych aktorów, którzy komunikowali się ze sobą.
Byłem jeszcze na imprezie powarsztatowej. Udało mi się odnowić parę starych znajomości, trochę nowych nawiązać, dostać opieprz, że nic nie piszę na blogu (a więc jednak ktoś to czyta i niektórym komuś to pomaga), ani że nie występuję na konferencjach (zwłaszcza tych zagranicznych, nie mówiąc już o polskich). No cóż będę się starał zmienić ten stan rzeczy no i mam nadzieję, że się uda.
Na koniec należą się jeszcze wielkie brawa dla organizatorów, wolontariuszy i sponsorów, za super warsztaty (choć sam może nie trafiłem na typowe warsztaty), dobrą atmosferę i możliwość dzielenia się wiedzą jak i jej zdobywaniem. Sieć po kablu bardzo dobrze działała, trochę może brakowało informacji, gdzie, jaka sala się znajduje. Rozumiem też, że wydarzenie było darmowe i budżet był nie wielki, ale zabrakło mi... herbaty. No cóż, nie każdy jest przecież kawowcem (choć w naszej branży to chyba tylko jakieś odosobnione wyjątki), ale w ogólnej ocenie całości można ten detal pominąć. Trzymajmy poziom, aby w przyszłym roku było co najmniej tak dobrze jak w tym.