JVM alati za praćenje performansi — komandna linija
Sećam se da sam 2014. godine, radeći na platformi za trgovinu terminskim ugovorima u velikim količinama, naišao na neke neugodne probleme — verovatno zato što moje znanje o konkurentnom programiranju nije bilo dovoljno čvrsto, što je dovelo do curenja memorije.
Tada sam dugo tražio rešenje; alat koji sam koristio bio je jconsole koji dolazi uz JDK. Nisam ranije imao slično iskustvo sa praćenjem performansi, pa mi je pronalaženje problema bilo veoma bolno i do danas mi je ostalo u snažnom sećanju.
Danas ćemo krenuti od alata i videti konkretnu upotrebu ovih komandnolinijskih alata i kako se njima pronalaze problemi.
JDK alati za praćenje performansi
Pored naših starih poznanika, komandi java i javac, u Java-inom bin direktorijumu postoji još mnogo drugih komandnolinijskih alata, recimo za praćenje performansi: jps, jstat, jinfo, jmap, jstack, jcmd itd.

Ja koristim macOS; korisnici Windowsa možda vide ekstenziju .exe, ali funkcionalnost je ista, pa neću posebno raditi snimke.
Sada ću vam redom predstaviti namene ovih alata, da ih malo bolje upoznate.
jps: pregled procesa virtuelne mašine
jps (Java Virtual Machine Process Status Tool) je sličan ps-u iz Linuxa; služi za brzi pregled toga koje Java aplikacije trenutno rade i koji su im PID-ovi, što je neophodno za dalju dijagnostiku drugim JVM alatima.
Format komande jps:
jps [ options ] [ hostid ]Primer komande jps:

①, obratite pažnju — treći proces je upravo moj lokalno pokrenuti praktični projekat tehnička frakcija (paicoding), zajednički projekat Spring Boot + React sa odvojenim frontom i bekEndom, koji je mnogim čitaocima pomogao da dobiju željene ponude za posao.
②, šta je pid? pid je identifikator procesa, jedinstveni oznacitelj koji operativni sistem dodeljuje procesu, pomoću kojeg se mogu pregledati detaljni podaci o procesu.
Obično proces zatvaramo preko X dugmeta u gornjem desnom uglu, ali sa pid-om možemo direktno, u komandnoj liniji, zatvoriti proces komandom kill, na primer:
kill -9 pidZnači — prisilno zatvori proces sa odgovarajućim pid-om; početnici, nemojte u produkcionom okruženju nekontrolisano kill-ovati (😂).
Pogledajmo još često korišćene opcije komande jps:
| Lista opcija | Opis |
|---|---|
| -q | Ispisuje samo ID procesa, zanemaruje informaciju o glavnoj klasi |
| -l | Ispisuje potpuno ime glavne klase, ili putanju JAR paketa za izvršavanje |
| -m | Ispisuje argumente prosleđene metodi main() glavne klase pri pokretanju procesa virtuelne mašine |
| -v | Ispisuje JVM parametre pri pokretanju procesa virtuelne mašine |
jstat: pregled runtime informacija JVM-a
jstat (Java Virtual Machine Statistics Monitoring Tool) služi za praćenje raznih runtime stanja JVM-a, pružajući podatke o garbage collection-u, učitavanju klasa, JIT kompilaciji i sl.
Format komande jstat:
jstat [ option vmid [interval[s|ms] [count]] ]Opcija option uglavnom se deli u tri kategorije: učitavanje klasa, garbage collection, stanje kompilacije tokom rada.
①, -class: praći broj učitanih i izbačenih klasa, ukupan prostor i vreme utrošeno na učitavanje klasa.
Komanda jstat -class -t 75952 1000 2 ispisuje podatke o učitavanju klasa procesa 75952, jednom u sekundi, ukupno dva puta.

- Loaded: broj učitanih klasa.
- Bytes: ukupan prostor koji zauzimaju sve učitane klase.
- Unloaded: broj izbačenih klasa.
- Time: vreme koje je potrošio class loader.
②, -gc: praći stanje Java hipa, uključujući kapacitet, iskorišćenost i vreme GC-a za Eden oblast, dve Survivor oblasti, staru generaciju itd.
Komanda jstat -gc 75952 1000 2 ispisuje GC podatke procesa 75952, jednom u sekundi, ukupno dva puta. Rezultata ima puno, pa sam ih presekao i saklopio radi lakšeg pregleda.

- S0C, S1C, S0U, S1U: veličina i iskorišćenost Survivor oblasti, jedna From, jedna To; C je tekuća veličina (Current), a U iskorišćena veličina (Used).
- EC, EU: veličina i iskorišćenost Eden oblasti.
- OC, OU: veličina i iskorišćenost stare generacije (Old).
- MC, MU: veličina i iskorišćenost Metaspace-a.
- GC, GCC: GC označava ukupan broj i ukupno vreme Minor GC-a (garbage collection mlade generacije); GCC označava ukupan broj i ukupno vreme Major GC-a (garbage collection stare generacije, poznat i kao Full GC).
③, -compiler: praći metode koje je JIT kompilator preveo, utrošeno vreme i sl.
- Compiled: broj prevedenih metoda.
- Failed: broj metoda čija je kompilacija neuspešna.
- Invalid: broj nevažećih prevedenih metoda.
- Time: vreme utrošeno na kompilaciju.
Komanda jstat -compiler 75952 1000 2 ispisuje podatke o kompilaciji procesa 75952, jednom u sekundi, ukupno dva puta.

Hajde da sumiramo glavne opcije jstat-a, u tabeli:
| Lista opcija | Opis |
|---|---|
| -class | Praći broj učitavanja/izbacivanja klasa, ukupan prostor i vreme utrošeno na učitavanje klasa |
| -gc | Praći stanje Java hipa, uključujući Eden, dve Survivor oblasti, staru generaciju, Metaspace itd.; kapacitet, iskorišćenost, ukupno vreme garbage collection-a itd. |
| -gccapacity | Sadržaj sličan -gc, ali izlaz je fokusiran na maksimalni i minimalni iskorišćeni prostor pojedinih oblasti Java hipa |
| -gcutil | Sadržaj uglavnom isti kao -gc, ali izlaz je fokusiran na procenat iskorišćenog prostora u odnosu na ukupan prostor |
| -gccause | Isti sadržaj kao -gcutil, ali dodatno ispisuje i uzrok poslednjeg garbage collection-a |
| -gcnew | Praći garbage collection mlade generacije |
| -gcnewcapacity | Sadržaj uglavnom isti kao -gcnew, fokusiran na maksimalni i minimalni iskorišćeni prostor |
| -gcold | Praći garbage collection stare generacije |
| -gcoldcapacity | Sadržaj uglavnom isti kao -gcold, fokusiran na maksimalni i minimalni iskorišćeni prostor |
| -compiler | Ispisuje metode koje je JIT kompilator preveo, utrošeno vreme i sl. |
| -printcompilation | Ispisuje metode koje su već prevodili JIT kompilatori |
jinfo: pregled konfiguracije virtuelne mašine
jinfo (Configuration Info for Java) služi za podešavanje parametara virtuelne mašine bez ponovnog pokretanja aplikacije, ili za ispis detaljnih informacija o Java procesu.
Format komande jinfo:
jinfo [ option ] pidKomanda jinfo -flags 88952 ispisuje podatke o JVM parametrima procesa 88952.

Međutim, nažalost, na mom macOS sistemu komanda jinfo nije htela da se uspešno izvrši; nakon raznih eksperimenata pronašao sam rešenje.
Mogući razlog jeste što sam na macOS imao instalirano previše verzija JDK-a, što je dovelo do neslaganja između verzije JDK-a u kojoj IntelliJ IDEA prevodi i verzije alata jinfo.
Kako to rešiti?
Pokušaj 1: kompilirajte i pokrenite Java program istom verzijom JDK-a, a zatim koristite jinfo iz te iste verzije JDK-a.

Rezultat je i dalje prijavio grešku, a mogući razlog je zastarela verzija JDK-a.

Pokušaj 2: testirajte sa JDK 11; kod neka kompilira i radi u JDK 11.

Zatim koristite jinfo iz JDK 11 — uspelo je.

Pokušajmo i komandu jinfo -flags 10025, i to prolazi.

Razlog što ovde delim i način rešavanja ovog problema jeste želja da čitaocima pružim neke smernice za rešavanje problema koji se javljaju u svakodnevnom razvoju.
jmap: izvoz snimka hipa
Komanda jmap služi za generisanje snimka hipa (obično poznatog kao heap dump ili dump fajl). Heap dump sadrži podatke o svim objektima u JVM hipu, uključujući klase, atribute, reference i sl. To je od velike pomoći pri analizi curenja memorije i optimizaciji korišćenja memorije.
Naravno, uloga jmap-a nije ograničena na to; on može i da prikaže iskorišćenost prostora hipa, koji je garbage collector trenutno u upotrebi i sl.
Format komande jmap:
jmap [ option ] vmidKomanda jmap -histo 10025 ispisuje zbirne podatke o broju svih objekata u hipu procesa 10025 i njihovoj zauzetosti memorije, sortirane po iskorišćenosti memorije.

Komanda jmap -dump:format=b,file=heap.hprof 10025 izvozi podatke snimka hipa procesa 10025 i čuva ih u fajl heap.hprof.

Ukratko objašnjenje ove komande:
- format: format fajla, ovde b, što označava binarni format.
- file: ime fajla.
Koje alate možemo koristiti za otvaranje ovog fajla? O tome će biti reči kasnije. Prvo pogledajmo glavne opcije jmap-a:
| Opcija | Opis |
|---|---|
| -dump | Generiše snimak Java hipa. |
| -finalizerinfo | Prikazuje objekte koji u F-Queue čekaju da Finalizer nit izvrši metod finalize. Linux platforma |
| -heap | Prikazuje detaljne podatke Java hipa, npr. koji se kolektor koristi, konfiguracija parametara, generacijsko stanje. Linux platforma |
| -histo | Prikazuje statistiku objekata u hipu, uključujući klase, broj instanci, ukupni kapacitet |
| -F | Kada proces virtuelne mašine ne odgovara na opciju -dump, može se prisilno generisati snimak. Linux platforma |
jstack: praćenje Java steka
jstack služi za ispis informacija o steku niti nekog procesa u JVM-u ili udaljene usluge za debugovanje (obično poznato kao threaddump ili javacore fajl). Često se koristi za dijagnostiku problema s nitima u aplikaciji, poput mrtve petlje (deadlock), beskonačne petlje ili dugog čekanja.
Format komande jstack:
jstack [ option ] vmidKomanda jstack -l 10025 ispisuje informacije o steku niti procesa 10025, uključujući podatke o bravama.

Glavne opcije alata jstack:
| Opcija | Opis |
|---|---|
| -F | Kada normalni zahtev za ispisom ne dobije odgovor, prinudno ispisuje stek niti |
| -l | Osim steka, prikazuje dodatne podatke o bravama |
| -m | Ako je u pitanju poziv nativnog metoda, može se prikazati c/c++ stek |
Hajde da preko primera mrtve petlje niti pogledamo upotrebu jstack-a.
Prvo, napišimo program sa mrtvom petljom:
class DeadLockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
System.out.println("Nit 1 je dobila brava 1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Nit 1 je dobila brava 2");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
System.out.println("Nit 2 je dobila brava 2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Nit 2 je dobila brava 1");
}
}
}).start();
}
}Kreirali smo dve niti, od kojih svaka pokušava da u različitom redosledu dobije dve brave (lock1 i lock2). Ovakvo razlikovanje redosleda dobijanja brava lako vodi u mrtvu petlju.
Pokrenimo ovaj kod — zaista se zaglavi.

Pokrenimo komandu jstack pid i videćemo informacije o nitima u mrtvoj petlji. Zaista ne laže!

jcmd: višenamenska komanda
jcmd je višenamenska komanda koja se može koristiti za prikupljanje snimka hipa, generisanje podataka o performansama JVM-a i Java aplikacije i dinamičku promenu pojedinih runtime parametara Jave. jcmd je moćniji od bilo koje pojedinačne komande poput jstack, jmap, jstat.
Na primer, komandom jcmd -l izlistavamo sve tekuće Java aplikacije, slično kao jps:

Na primer, komandom jcmd 10025 help prikazujemo komande koje podržava proces 10025:

Na primer, komandom jcmd 10025 VM.flags prikazujemo JVM parametre procesa 10025, što je ekvivalentno jinfo -flags 10025:

Na primer, komandom jcmd 10025 Thread.print prikazujemo podatke o nitima procesa 10025, što je ekvivalentno jstack 10025:

Format komande jcmd:
jcmd <pid | main class> <command ... | PerfCounter.print | -f file>Glavne opcije jcmd-a:
| Opcija | Opis | Dodatno |
|---|---|---|
| help | Ispisuje pomoć, primer: jcmd help [] | nema |
| ManagementAgent.stop | Zaustavlja JMX Agent | nema |
| ManagementAgent.start_local | Pokreće lokalni JMX Agent | nema |
| ManagementAgent.start | Pokreće JMX Agent | nema |
| Thread.print | Parametar -l ispisuje podatke o java.util.concurrent bravama, ekvivalentno: jstack | nema |
| PerfCounter.print | Ekvivalentno: jstat -J-Djstat.showUnsupported=true -snap | nema |
| GC.class_histogram | Ekvivalentno: jmap -histo | nema |
| GC.heap_dump | Ekvivalentno: jmap -dump:format=b,file=xxx.bin | nema |
| GC.run_finalization | Ekvivalentno: System.runFinalization() | nema |
| GC.run | Ekvivalentno: System.gc() | nema |
| VM.uptime | Parametar -date ispisuje tekuće vreme; vreme od pokretanja VM-a do sada, u sekundama | nema |
| VM.flags | Parametar -all ispisuje sve, ekvivalentno: jinfo -flags , jinfo -flag | nema |
| VM.system_properties | Ekvivalentno: jinfo -sysprops | nema |
| VM.command_line | Ekvivalentno: jinfo -sysprops | grep command |
| VM.version | Ekvivalentno: jinfo -sysprops | grep version |
Alati operativnog sistema
Pored komandnolinijskih alata koji dolaze uz JDK, često moramo da koristimo i komandnolinijske alate koje pruža operativni sistem radi praćenja performansi.
Na primer top, vmstat, iostat, netstat itd.
top: prikaz ukupne iskorišćenosti resursa sistema
Komanda top u realnom vremenu prikazuje iskorišćenost resursa pojedinih procesa u sistemu, kao što su CPU i iskorišćenost memorije. Često se koristi za brzi pregled toga koji procesi troše više resursa.
Izlaz ove komande menja se u realnom vremenu; možete izaći sa ctrl + c. Na slici ispod je izlaz na mom macOS-u:

Izlaz komande top može se podeliti u dva dela: u prvom delu su statistički podaci sistema, a u drugom podaci o procesima.
Statistički podaci
Statistički podaci odnose se na ceo sistem i uglavnom obuhvataju opterećenje sistema, iskorišćenost CPU-a, iskorišćenost memorije, korišćenje virtuelne memorije, mrežu i disk itd.
- Red 1 sadrži podatke o procesima i nitima: ukupan broj procesa, broj procesa u radu, broj procesa u spavanju, broj niti.
- Red 2 sadrži podatke o balansiranju opterećenja i iskorišćenosti CPU-a.
Load Avg: 4.02, 3.89, 3.29: prosečno opterećenje sistema u poslednjih 1, 5 i 15 minuta. Opterećenje veće od 3 znači da je sistem relativno zauzet;CPU usage: 6.97% user, 3.54% sys, 89.47% idle: korisnici zauzimaju 6,97% CPU-a, sistem 3,54%, dok je 89,47% CPU-a u praznom hodu. - Red 3 sadrži podatke o memoriji zajedničkih biblioteka (Shared Libraries). Podaci u ovom redu uglavnom se odnose na zajedničke biblioteke koje je učitao operativni sistem (dinamički linkovane biblioteke ili objektni fajlovi za deljenje).
- Red 4 sadrži podatke o korišćenju memorijskih oblasti (Memory Regions). Memorijska oblast jeste blok memorije koji operativni sistem dodeljuje aplikacijama i procesima. Svaka memorijska oblast ima posebnu namenu i osobine, npr. kod, podaci, hip, stek itd. Podaci u ovom redu pružaju detaljniji pregled korišćenja sistemske memorije.
- Red 5 sadrži podatke o korišćenju memorije,
PhysMem: 30G used (3018M wired), 1547M unused: ukupno je iskorišćeno 30 GB memorije, a preostalo je oko 1547 MB neiskorišćene memorije; - Red 6 sadrži podatke o virtuelnoj memoriji; virtuelna memorija je tehnika upravljanja memorijom računara koja svakom programu obezbeđuje jedan „virtuelni” adresni prostor. Za svaki program ti adresni prostori su neprekidni, ali u stvarnosti mogu biti raspoređeni po fizičkoj memoriji i prostoru za razmenu na disku (swap space).
- Red 7 sadrži mrežne podatke i podatke o disku,
Networks: packets: 22655692/19G in, 19180791/11G out: mreža je primila 19 GB paketa, a poslala oko 11 GB paketa;Disks: 14866544/288G read, 15176739/251G written: disk je očitao 14866544 puta i upisao oko 15176739 puta.
Podaci o procesima
U oblasti podataka o procesima prikazuje se iskorišćenost resursa pojedinih procesa u sistemu. Značenja glavnih polja:
- PID: identifikator procesa
- COMMAND: ime komande/komandna linija
- %CPU: iskorišćenost CPU-a procesa
- TIME: ukupno procesorsko vreme procesa, u jedinicama od 1/100 sekunde
- MEM: veličina fizičke i virtuelne memorije koju proces koristi, u KB
Korisnici Windowsa mogu koristiti komandu tasklist za pregled podataka o procesima.
vmstat: praćenje memorije i CPU-a
vmstat je alat za praćenje performansi na Linuxu, sa prilično potpunim mogućnostima. On može prikazivati statistike o iskorišćenosti CPU-a, memorije i swap-a.
Alat vmstat se obično koristi sa dva numerička argumenta: prvi je interval uzorkovanja u sekundama, a drugi broj uzoraka, na primer:

Komanda vmstat 1 3 uzorkuje jednom u sekundi, ukupno tri puta.
Značenja pojedinih kolona u izlazu:

Upotreba vmstat-a:
vmstat [options] [delay [count]]Glavne opcije vmstat-a:
[options]: nude različite izlazne opcije, npr. -a prikazuje aktivnu i neaktivnu memoriju, -d prikazuje statistiku diska, -s prikazuje statistiku memorije itd.[delay]: u kontinuiranom režimu, vreme zadrške između dva izveštaja (sekunde).[count]: broj izveštaja koje treba prikazati.
iostat: praćenje iskorišćenosti IO-a
iostat služi za prikaz statistika o iskorišćenosti CPU-a i IO podataka diska.

Osnovna upotreba:
iostat [options] [interval [count]][options]: nude različite izlazne opcije. Na primer, -c prikazuje iskorišćenost CPU-a, -d prikazuje iskorišćenost diska, -x prikazuje proširene statistike itd.[interval]: vreme zadrške između izveštaja (sekunde).[count]: broj prikazanih izveštaja.
Izlaz iostat obuhvata dva glavna dela:
Iskorišćenost CPU-a:
user: procenat procesorskog vremena koje koriste korisnički programi.system: procenat procesorskog vremena koje koriste programi na nivou sistema (jezgra).idle: procenat vremena u kojem je CPU u praznom hodu.
Statistike diska I/O:
tps(Transfers Per Second): broj prenosa u sekundi.kB_read/s: broj kilobajtova pročitanih u sekundi.kB_wrtn/s: broj kilobajtova upisanih u sekundi.kB_readikB_wrtn: ukupan broj kilobajtova pročitanih, odnosno upisanih.
Uz opciju
-xprikazuju se detaljnije statistike, na primer:%util: stepen zauzetosti diska.await: prosečno vreme čekanja I/O zahteva (milisekunde).svctm: vreme usluge, odnosno prosečno vreme potrebno za izvršenje jednog I/O zahteva.
Primeri upotrebe:
①, pregled osnovnih I/O statistika CPU-a i svih diskovnih uređaja:
iostat②, pregled statistika diska I/O, uz osvežavanje na svaka 2 sekunde:
iostat -dx 2③, pregled samo iskorišćenosti CPU-a:
iostat -cnetstat: praćenje korišćenja mreže
netstat (network statistics) služi za praćenje i prikaz podataka povezanih sa mrežom. Osnovna upotreba:
netstat [options][options]: nude različite izlazne opcije. Uobičajene opcije obuhvataju-a(prikaz svih veza i portova koji slušaju),-t(prikaz TCP veza),-u(prikaz UDP veza),-n(prikaz adresa i portova u numeričkom obliku),-r(prikaz tabele rutiranja) itd.

Izlaz netstat obično obuhvata sledeće informacije:
①, Mrežne veze: prikazuje aktivne ili portove koji slušaju, uključujući ime usluge, lokalnu adresu i port, udaljenu adresu i port, stanje veze itd.
②, Tabela rutiranja: prikazuje mrežnu tabelu rutiranja, uključujući ciljnu adresu, mrežni prolaz, mrežnu masku, korišćeni interfejs itd.
Kratak pregled
Danas smo predstavili alate za praćenje performansi koji dolaze uz JDK, kao i neke komandnolinijske alate koje pruža operativni sistem.
Ovi alati su od velike pomoći u pronalaženju problema; nadam se da ćete ih svakako savladati, za svaki slučaj.
Reference
- Gostujući autor Sanfene: JVM performans monitoring — komandna linija
