V tomto článku budeme pokračovat v popisu nástrojů, které je možné využít pro monitorování aplikací běžících ve virtuálním stroji Javy (JVM). Minule jsme se zabývali především základními nástroji ovládanými z příkazové řádky, dnešní téma se naopak týká nástrojů s plnohodnotným grafickým uživatelským rozhraním, konkrétně aplikace nazvané jconsole. V souvislosti s nástrojem jconsole se taktéž zmíním o technologii JMX. Při popisu vlastností jednotlivých nástrojů se zaměříme především na OpenJDK7, i když naprostá většina dále popisovaných nástrojů bude využitelná i při použití OpenJDK6 a Oracle JDK 6 a 7.

Monitorovací nástroje ovládané z příkazové řádky – zhodnocení

úvodní části tohoto článku jsme se zabývali popisem základních nástrojů, které je možné využít při monitorování aplikací běžících ve virtuálním stroji Javy (JVM). Tyto nástroje jsou poměrně důležité, a to jak pro programátory a testery, tak pro administrátory, kteří se starají o (v ideálním případě bezproblémový) běh javovských aplikací na počítačích klientů a především na aplikačních serverech. Programovací jazyk Java sice programátorům usnadňuje jejich práci díky větší abstrakci nad konkrétním počítačem (automatická správa paměti, základní synchronizace vláken na úrovni jazyka, unifikované I/O atd.), na druhou stranu je však především u rozsáhlejších aplikací či u aplikací, které mají pracovat nepřetržitě, nutné průběžně sledovat zatížení celého systému.

Základní monitorovací nástroje pro JVM jsou dostupné již v základní instalaci vývojového prostředí Javy, tj. v JDK (Java Development Kit). Jinými slovy to znamená, že na monitorovaném stroji nemusí být ve skutečnosti kromě JDK nainstalován žádný další pomocný nástroj a v případě, že je nainstalováno pouze JRE (Java Runtime Environment), lze příslušné nástroje pouze zkopírovat (nejsou zde žádné další závislosti). Mezi již popsané monitorovací aplikace patří především jstack, jmap a jhat, k nimž se ještě přidává pomocná aplikace jps. Mezi přednosti těchto pomocných aplikací patří zejména již zmíněný fakt, že je nalezneme v prakticky každé instalaci Javy a taktéž to, že je možné relativně snadno tyto nástroje ovládat přes jakékoli zařízení (počítač, smartphone, …) schopné se připojit k monitorovanému počítači přes ssh.

Přednosti a zápory nástrojů s plnohodnotným GUI

Kromě pomocných aplikací jstack, jmap a jhat postupně vzniklo i poměrně velké množství dalších monitorovacích nástrojů, které svým uživatelům (programátorům, testerům či administrátorům) nabízí plnohodnotné grafické uživatelské rozhraní. Použití GUI má oproti CLI (command line interface) své přednosti, ale samozřejmě i některé zápory. Asi největší předností prakticky všech monitorovacích nástrojů s grafickým uživatelským rozhraním je jejich větší přehlednost, snadnější ovládání pro uživatele, kteří nástroj používají méně často, a v neposlední řadě taktéž fakt, že se na obrazovce může zobrazit a vzájemně odlišit větší množství informací, včetně grafů, které mají mnohdy větší vypovídací hodnotu než pouhé tabulky s výsledky.

Navíc je většinou možné s těmito informacemi přímo interaktivně manipulovat, jak si ostatně ukážeme v navazujících kapitolách. Na druhou stranu je však pro provoz těchto aplikací nutné využívat počítače s podporou GUI, což většinou znamená, že se tyto aplikace neprovozují přímo na (aplikačních) serverech. To však nemusí být na škodu, protože například v případě nástroje jconsole a ve větší míře pak nástroje Thermostat (popsaného ve třetí části seriálu) je možné se připojit na vzdálený virtuální stroj Javy, což znamená, že monitorovací nástroj (resp. přesněji řečeno jeho část s grafickým uživatelským rozhraním) je provozována na straně klienta, zatímco monitorovaná JVM a „agentská“ část monitorovacího nástroje může běžet zcele nezávisle na vzdáleném stroji.

Jconsole

Prvním monitorovacím nástrojem vybaveným plnohodnotným grafickým uživatelským rozhraním, s nímž se dříve či později pravděpodobně setká většina javovských programátorů a především administrátorů javovských aplikací, je velmi jednoduše ovladatelný a na systémové zdroje poměrně nenáročný nástroj nazvaný jconsole. Tato aplikace, která je přímo součástí instalace Javy (tj. základního JDK), umožňuje sledování javovských aplikací běžících jak na stejném počítači, kde je jconsole spuštěna, tak i na vzdáleném stroji, což může být například pro administrátory velmi cenná vlastnost. V současné verzi však v tomto nástroji chybí některé důležité funkce (podrobnější sledování vláken či haldy – heapu), takže je vhodné jconsoli používat společně s dalšími pomocnými nástroji.

Jednotlivé funkce, které aplikace jconsole svým uživatelům nabízí, budou vysvětleny na sérii snímků obrazovky zobrazených pod tímto odstavcem. Pod každým screenshotem je napsán komentář vysvětlující funkci hlavních prvků grafického uživatelského rozhraní jconsole.

Obrázek 1: Nástroj jconsole je možné vyvolat přímo z příkazového řádku pomocí příkazu jconsole. Tento příkaz by měl být dostupný ihned po instalaci JDK. Na mnoha distribucích Linuxu je většinou tento příkaz umístěn v adresáři /usr/lib/jvm/verze_javy/bin, na nějž vede symbolický odkaz z /usr/bin (popř. se může jednat i o odkaz nepřímý a to v případě, že se pro správu více variant instalací Javy používá alternatives, což je i případ Fedory). Navíc se ve Fedoře (konkrétně v Gnome Shellu) tato aplikace skrývá pod názvem „OpenJDK Monitoring & Management Console“.

Obrázek 2: Po spuštění grafického rozhraní nástroje jconsole se v úvodním dialogu zobrazí seznam běžících javovských aplikací, popř. je možné se připojit na vzdáleně běžící aplikaci (postup si vysvětlíme příště). Na tomto obrázku byla vybrána lokální javovská aplikace pojmenovaná Main podle jména spuštěné třídy. Ve sloupci PID se vypisuje stejná hodnota, jakou získáme nástrojem jps volaným z příkazové řádky (viz též předchozí část tohoto seriálu). Vzhledem k tomu, že i samotná jconsole je naprogramovaná v Javě, najdeme ji v tomto dialogu taktéž.

Obrázek 3: Po stisku tlačítka [Connect] se nástroj jconsole pokusí připojit se k vybranému virtuálnímu stroji Javy a v něm k vybrané aplikaci. Vzhledem k tomu, že se v rámci připojování většinou provádí i inicializace JMX (Java Management Extensions) a popř. i RMI (Remote Method Invocation), může připojování k virtuálnímu stroji s běžící aplikací určitou dobu trvat – na moderních průměrně vytížených počítačích počítejte s jednotkami sekund, výjimečně s desítkami sekund.

Údaje zobrazované na listech (tabech) nástroje jconsole

Po připojení jconsole k virtuálnímu stroji Javy, na němž běží zvolená aplikace, se v okně nástroje jconsole vytvoří nové podokno se šesti listy (taby) s názvy „Overview“, „Memory“, „Threads“, „Classes“, „VM Summary“ a „MBeans“. Jejich funkce je opět vysvětlena na sérii snímků obrazovky, kromě posledního listu, jemuž je věnována samostatná kapitola na konci tohoto článku.

Obrázek 4: Na prvním listu nazvaném „Overview“ se zobrazuje čtveřice grafů používajících shodnou časovou osu. Na nejvyšším místě je zobrazen graf s využitím haldy/heapu (tj. oblasti paměti používané pro ukládání prakticky všech objektů), pod ním je umístěn graf s počtem vláken, graf s vyneseným počtem načtených tříd a konečně graf zobrazující vytížení procesoru (pokud je však okno jconsole dostatečně široké, zobrazí se grafy vedle sebe, popř. do matice 2×2). Jednotky pro časovou osu je možné zvolit ve výběrovém seznamu zobrazeném nad grafy a zajímavé je, že jednou z možných jednotek je i jeden rok, takže je možné teoreticky sledovat i dlouhotrvající aplikace, například aplikační servery atd.

Obrázek 5: List nazvaný „Memory“ se stává velmi důležitým v okamžiku, kdy vývojář či programátor potřebuje zjistit obsazení paměti virtuálním strojem Javy. V horní části se nachází graf, jehož zjednodušenou verzi používal i list „Overview“ z předchozího snímku. Zde je však možné si zvolit, jaké údaje má graf zobrazovat – zda se má například zobrazit obsazení celé haldy, popř. zda administrátora spíše zajímá obsazení některé z paměťových oblastí, na něž je halda rozdělena.

Obrázek 6: Volba údajů pro zobrazení na grafu se provádí buď z výběrového seznamu, popř. z menšího sloupcového grafu zobrazeného v pravém dolním rohu. Co tento graf zobrazuje, lze zjistit velmi snadno z nápovědy po najetí myší nad některou buňku tohoto grafu – ovšem záleží na tom, jaký správce paměti je aktuálně používán. Na tomto listu se však taktéž nachází tlačítko [Perform GC] se zřejmým významem – po jeho stisku se v samostatném vláknu zavolá správce paměti a jak je vidět na tomto snímku (poslední část grafu), skutečně se po jeho zavolání snížilo celkové obsazení haldy.

Obrázek 7: Na listu nazvaném „Threads“ se zobrazují informace o všech vláknech, které existují v monitorované javovské aplikaci. Navíc je možné u každého vlákna zobrazit jeho stav (běžící vlákno, pozastavené vlákno atd.) a taktéž, což je mnohdy velmi důležité, i stručný obsah jeho zásobníkových rámců – takzvaný stack trace, s nímž se každý javovský vývojář či administrátor setkal při vzniku výjimky. Tlačítko pro detekci deadlocku může v některých případech zjistit, zda dvě či více vláken nečeká na společně sdílené prostředky (ne vždy je tato detekce úspěšná, navíc někdy detekce falešně indikuje deadlock, i když k němu ještě nedošlo).

Obrázek 8: Obsah čtvrtého listu nazvaného „Classes“ je zřejmý již ze svého názvu – zobrazují se zde třídy načtené do virtuálního stroje Javy a umístěné ve speciální paměťové oblasti nazvané „PermGen“. Tento list lze využít při vyčerpání paměťové oblasti „PermGen“ pro zjištění, kdy k tomuto problému dochází (ovšem některé nové verze JDK/JRE už obsahují jinou variantu virtuálního stroje Javy, který klasický „PermGen“ nepotřebuje).

Obrázek 9: Pátý list nazvaný „VM Summary“ obsahuje souhrnné informace o virtuálním stroji Javy, o konfiguraci tohoto stroje (použitý správce paměti atd.) i základní informace o operačním systému, na němž je virtuální stroj spuštěn. Neméně důležité jsou informace o nastavení cest k nativním knihovnám i javovským knihovnám (CLASSPATH), protože především při spouštění aplikačního serveru nemusí být administrátorovi při hledání problémů zcela zřejmé, které konkrétní cesty jsou skutečně použity.

Podpora technologie JMX (MBeans) v nástroji jconsole

Poslední list zobrazovaný nástrojem jconsole se jmenuje „MBeans“. Na tomto listu se zobrazují informace dostupné (přečtené) z monitorované aplikace, některé hodnoty je dokonce možné i měnit. Pro čtení informací a jejich změnu se používá rozhraní nabízené technologií JMX neboli Java Management Extensions. Tato poměrně jednoduše využitelná technologie umožňuje průběžně sledovat různé hodnoty nastavované aplikací či samotným virtuálním strojem, měnit tyto hodnoty a popř. i volat vybrané metody – to vše nepřímo, například právě s nástrojem jconsole. Virtuální stroje Javy, které tuto technologii podporují (včetně Oracle JDK 6/7 a OpenJDK 6/7), umožňují, aby se k nim kdykoli za běhu připojila jiná aplikace (většinou se jedná o monitorovací nástroj) přes takzvané Attach API.

V předchozích verzích běhového prostředí Javy (JRE) bylo nutné tuto funkci explicitně povolit, například nastavením systémové vlastnosti com.sun.management.jmxremote při startu virtuálního stroje Javy, ovšem od Java SE 6 to již není nutné a každý spuštěný virtuální stroj Javy by měl JMX nabízet zcela automaticky. Jednou z javovských aplikací využívajících poměrně intenzivně rozhraní JMX je nerelační databáze nazvaná MongoDB. Díky využití JMX a MBeans je možné relativně snadno sledovat i nastavovat vlastnosti MongoDB přímo z jconsole, a není tak nutné spouštět či vůbec vyvíjet speciální nástroj. Nesmíme zapomenout ani na JBoss, který MBeans taktéž poměrně intenzivně používá.

Obrázek 10: MBeans aplikace MongoDB zobrazené v nástroji jconsole.

Demonstrační program využívající technologii JMX

V této kapitole si ukážeme velmi jednoduchý demonstrační program, který s využitím technologie JMX nabídne monitorovacím aplikacím dvě hodnoty (načítané přes gettery a nastavované s využitím setterů) a taktéž metodu, kterou bude možné přímo z jconsole zavolat. Demonstrační příklad se bude skládat pouze ze tří zdrojových souborů. Bude se jednat především o rozhraní nazvané TestMBean (přípona „MBean“ je zde velmi důležitá a neměla by se měnit), dále pak o třídu nazvanou Test, která implementuje metody předepsané v rozhraní TestMBean a poslední zdrojový kód obsahuje třídu nazvanou Main, v níž se vytvoří takzvaný MBean server, zaregistruje se v něm příslušná třída Test a nakonec program (přesněji řečeno jeho hlavní vlákno) bude čekat na své ukončení v režimu sleep.

V rozhraní nazvaném TestMBean jsou předepsány pouze hlavičky (signatury) čtyř metod. Konkrétně se jedná o dva gettery, jeden setter a taktéž o metodu s libovolným jiným jménem, které by nemělo začínat na get… a set… Gettery a setter budou použity v nástroji jconsole pro zobrazení a změnu příslušných hodnot a metodu bude taktéž možné přes jconsoli zavolat – to si ostatně ukážeme ihned v následující kapitole.

Obrázek 11: Zdrojový kód rozhraní TestMBean.

Zdrojový kód třídy Test je poměrně jednoduchý. Povšimněte si především toho, že se v této třídě plně implementuje rozhraní TestMBean, tj. jsou zde těla všech metod předepsaných tímto rozhraním a navíc je do setteru i do obou getterů přidán příkaz pro výpis zprávy na standardní výstup, aby bylo patrné, že jconsole skutečně bude tyto metody volat. Totéž ostatně platí pro metodu callMethod, jejíž jediná činnost spočívá ve výpisu zprávy na standardní výstup.

Obrázek 12: Zdrojový kód třídy Test implementující rozhraní TestMBean.

Konečně se dostáváme ke třetímu a současně i poslednímu zdrojovému kódu – ke třídě Main. Ta obsahuje pouze statickou veřejnou metodu main(), která se najde a spustí při inicializaci aplikace ve virtuálním stroji Javy. V této metodě se nejprve získá MBean server nabízený virtuálním strojem Javy, vytvoří se instance třídy Test a následně se tato instance v MBean serveru zaregistruje pod jménem Test (třída ObjectName použitá při registraci umožňuje specifikovat jméno či jména poměrně komplikovaným způsobem, to však již překračuje zaměření tohoto článku). A to je vlastně vše – zbývá již jen zajistit, aby aplikace běžela tak dlouho, aby ji bylo možné sledovat z jconsole, což je úkol posledního příkazu Thread.sleep(Long.MAX_VALUE).

Obrázek 13: Zdrojový kód třídy Main.

Sledování činnosti demonstračního programu v nástroji jconsole

Nyní si již můžeme vyzkoušet, jakým způsobem je možné v nástroji jconsole prakticky použít list nazvaný „MBeans“. Nejprve je nutné přeložit a spustit náš demonstrační příklad popsaný v předchozí kapitole. To je ve skutečnosti velmi jednoduché, alespoň v případě, že se používá Java SE 6 či Java SE 7 (v předchozích verzích JDK/JRE to totiž bylo poněkud komplikovanější). Překlad zajistí příkaz

javac *.java

zavolaný v adresáři, kde jsou uloženy zdrojové kódy rozhraní TestMBean i obou tříd Test a Main. V těchto zdrojových kódech nebyl explicitně specifikován žádný balíček, což mj. znamená, že nejsou kladeny žádné speciální požadavky na název adresáře, v němž mají být zdrojové texty uloženy.

Následně je nutné demonstrační program spustit příkazem

java Main

Pokud použitý virtuální stroj podporuje technologii JMX/MBean, měla by se na standardní výstup vypsat pouze zpráva o tom, že program čeká na své ukončení. V opačném případě se vypíše výjimka s dalšími informacemi o tom, z jakého důvodu se nepodařilo server MBean získat, popř. proč nebylo možné do něj zaregistrovat instanci naší třídy Test.

Ve druhém kroku z dalšího terminálu (konzole) spustíme nástroj jconsole a v úvodním dialogu vybereme aplikaci nazvanou Main – viz též obrázky číslo 1 a 2 uvedené hned na začátku tohoto článku. Mělo by dojít k připojení k virtuálnímu stroji Javy, v němž aplikace Main běží, což může trvat několik sekund, výjimečně několik desítek sekund. Až dojde k úspěšnému připojení, přepněte se na list MBeans, na němž by měl být v levé části zobrazen strom obsahující kromě dalších informací (nabízených většinou přímo virtuálním strojem Javy) i uzel nazvaný Test – přesně toto jméno jsme specifikovali při registraci do serveru MBeans metodou MBeanServer.registerMBean():

Obrázek 14: Obsah listu MBean po připojení nástroje jconsole k běžícímu demonstračnímu příkladu. Bude nás zajímat pouze uzel Test.

Obrázek 15: Po rozbalení uzlu Test uvidíme dvojici poduzlů nazvaných Attributes a Operations. V poduzlu Attributes jsou zobrazeny obě proměnné čtené (popř. nastavované) přes příslušné gettery a setter.

Obrázek 16: Čtení a modifikace atributu nazvaného Value se interně provádí přes příslušný getter a setter – to, že se getter či setter skutečně zavolá, zjistíme snadno při pohledu na terminál/konzoli, kde běží aplikace Main.

Obrázek 17: Volání metody callMethod() přímo z nástroje jconsole. Pokud se klikne na tlačítko [callMethod()], měla by se na konzoli s běžící aplikací Main vypsat zpráva o tom, že tato metoda byla skutečně z jconsole zavolána.