Programování

Diagnostikujte běžné problémy za běhu s hprof

Úniky paměti a zablokování a prasata CPU, ach bože! Vývojáři aplikací Java často čelí těmto runtime problémům. Mohou být obzvláště skličující v komplexní aplikaci s více vlákny běžícími přes stovky tisíc řádků kódu - aplikace, kterou nemůžete odeslat, protože roste v paměti, stává se neaktivní nebo pohltí více cyklů CPU, než by měla.

Není žádným tajemstvím, že nástroje pro profilování Java mají dlouhou cestu, aby dohnaly své protějšky v alternativním jazyce. Nyní existuje mnoho výkonných nástrojů, které nám pomáhají vypátrat viníky těchto běžných problémů. Jak však získáte důvěru ve schopnost efektivně používat tyto nástroje? Koneckonců, používáte nástroje k diagnostice složitého chování, kterému nerozumíte. Abychom vyjádřili vaši nepříjemnou situaci, jsou data poskytovaná nástroji poměrně složitá a informace, na které se díváte nebo které hledáte, nejsou vždy jasné.

Když jsem čelil podobným problémům ve své předchozí inkarnaci jako experimentální fyzik, vytvořil jsem kontrolní experimenty s předvídatelnými výsledky. To mi pomohlo získat důvěru v měřicí systém, který jsem použil v experimentech, které generovaly méně předvídatelné výsledky. Podobně tento článek používá nástroj pro profilování hprof k prozkoumání tří jednoduchých řídicích aplikací, které vykazují tři běžné problémové chování uvedené výše. I když není tak uživatelsky přívětivý jako některé komerční nástroje na trhu, hprof je součástí Java 2 JDK a jak ukážu, dokáže toto chování efektivně diagnostikovat.

Běh s hprof

Spuštění programu s hprof je snadné. Jednoduše vyvoláte běhový modul Java pomocí následující možnosti příkazového řádku, jak je popsáno v dokumentaci nástroje JDK pro spouštěč aplikací Java:

java -Xrunhprof [: help] [: =, ...] MyMainClass 

Seznam možností je k dispozici u [:Pomoc] zobrazena možnost. Generoval jsem příklady v tomto článku pomocí Blackdown portu JDK 1.3-RC1 pro Linux pomocí následujícího spouštěcího příkazu:

java -classic -Xrunhprof: halda = stránky, cpu = vzorky, hloubka = 10, monitor = y, vlákno = y, doe = y MemoryLeak 

Následující seznam vysvětluje funkci každé možnosti použité v předchozím příkazu:

  • halda = stránky: Říká hprof, aby generoval trasování zásobníku označující, kde byla přidělena paměť
  • cpu = vzorky: Říká hprof, aby pomocí statistického vzorkování určil, kde CPU tráví čas
  • hloubka = 10: Říká hprof, aby zobrazoval stopy zásobníku maximálně 10 úrovní
  • monitor = y: Říká společnosti hprof, aby generovala informace o monitorech konfliktů používaných k synchronizaci práce více vláken
  • závit = y: Říká hprof, aby identifikoval vlákna ve stopách zásobníku
  • doe = y: Říká hprof, aby po ukončení vytvořil výpis profilujících dat

Pokud používáte JDK 1.3, musíte vypnout výchozí kompilátor HotSpot pomocí -klasický volba. HotSpot má svůj vlastní profiler vyvolaný prostřednictvím -Xprof možnost, která používá výstupní formát odlišný od toho, který zde popíšu.

Spuštění programu s hprof ponechá soubor s názvem java.hprof.txt ve vašem pracovním adresáři; tento soubor obsahuje informace o profilování shromážděné během spuštění programu. Výpis můžete také vygenerovat kdykoli, když je váš program spuštěn, stisknutím Ctrl- \ v okně konzoly Java v systému Unix nebo Ctrl-Break v systému Windows.

Anatomie výstupního souboru hprof

Výstupní soubor hprof obsahuje části popisující různé charakteristiky profilovaného programu Java. Začíná to záhlaví, které popisuje jeho formát, jehož nároky v záhlaví se mohou změnit bez předchozího upozornění.

Sekce Thread and Trace výstupního souboru vám pomohou zjistit, která vlákna byla aktivní, když váš program běžel, a co udělali. Sekce Vlákno poskytuje seznam všech vláken spuštěných a ukončených během životnosti programu. Sekce Trace obsahuje seznam očíslovaných trasování zásobníku pro některá vlákna. Na tato čísla trasování zásobníku se odkazuje v jiných oddílech souborů.

Oddíly haldy a weby vám pomohou analyzovat využití paměti. Záleží na halda možnost, kterou vyberete při spuštění virtuálního počítače (VM), můžete získat výpis všech živých objektů v haldě Java (halda = výpis) a / nebo seřazený seznam alokačních webů, který identifikuje nejvíce přidělené objekty (halda = stránky).

Sekce CPU CPU a čas CPU vám pomohou pochopit využití CPU; část, kterou získáte, závisí na vašem procesor suboption (cpu = vzorky nebo CPU = čas). CPU Samples poskytuje statistický profil provádění. CPU Time zahrnuje měření, kolikrát byla daná metoda volána a jak dlouho trvalo provedení každé metody.

Sekce Monitor Time a Monitor Dump vám pomohou pochopit, jak synchronizace ovlivňuje výkon vašeho programu. Monitor Time ukazuje, kolik času vaše vlákna zažívají sváření o uzamčené prostředky. Monitor Dump je snímek aktuálně používaných monitorů. Jak uvidíte, Monitor Dump je užitečný pro nalezení zablokování.

Diagnostikujte únik paměti

V Javě definuji únik paměti jako (obvykle) neúmyslné selhání dereference vyřazených objektů, aby sběrač odpadků nemohl získat zpět paměť, kterou používají. The MemoryLeak program v seznamu 1 je jednoduchý:

Výpis 1. Program MemoryLeak

01 import java.util.Vector; 02 03 veřejná třída MemoryLeak {04 05 public static void main (String [] args) {06 07 int MAX_CONSUMERS = 10 000; 08 int SLEEP_BETWEEN_ALLOCS = 5; 09 10 ConsumerContainer objectHolder = nový ConsumerContainer (); 11 12 while (objectHolder.size () <MAX_CONSUMERS) {13 System.out.println ("Allocating object" + 14 Integer.toString (objectHolder.size ()) 15); 16 objectHolder.add (new MemoryConsumer ()); 17 try {18 Thread.currentThread (). Sleep (SLEEP_BETWEEN_ALLOCS); 19} catch (InterruptedException ie) {20 // Nedělat nic. 21} 22} // zatímco. 23} // hlavní. 24 25} // Konec MemoryLeak. 26 27 / ** Pojmenovaná třída kontejneru pro uložení odkazů na objekty. Třída * / 28 ConsumerContainer rozšiřuje třídu Vector {} 29 30 / **, která spotřebovává pevné množství paměti. * / 31 třída MemoryConsumer {32 public static final int MEMORY_BLOCK = 1024; 33 veřejných bajtů [] memoryHoldingArray; 34 35 MemoryConsumer () {36 memoryHoldingArray = nový bajt [MEMORY_BLOCK]; 37} 38} // Ukončit spotřebitel paměti. 

Při spuštění programu vytvoří a ConsumerContainer objekt, poté začne vytvářet a přidávat MemoryConsumer namítá proti tomu objekty o velikosti alespoň 1 kB ConsumerContainer objekt. Díky udržování přístupnosti objektů jsou nedostupné pro uvolňování paměti, simulující únik paměti.

Podívejme se na vybrané části souboru profilu. První řádky sekce Weby jasně ukazují, co se děje:

SITES BEGIN (seřazeno podle živých bajtů) Po 3. září 19:16:29 2001 procent živého alokačního zásobníku Třída pořadí vlastní hromadné bajty objs bajty objs název stopy 1 97,31% 97,31% 10280000 10000 10280000 10 000 1995 [B 2 0,39% 97,69% 40964 1 81880 10 1996 [L; 3 0,38% 98,07% 40000 10 000 40000 10 000 1994 Paměť Spotřebitel 4 0,16% 98,23% 16388 1 16388 1 1295 [C 5 0,16% 98,38% 16388 1 16388 1 1304 [C ... 

Existuje 10 000 objektů typu byte[] ([B ve VM-speak) stejně jako 10 000 MemoryConsumer předměty. Bajtová pole zabírají 10 280 000 bajtů, takže zjevně existuje režie těsně nad surovými bajty, které každé pole spotřebovává. Vzhledem k tomu, že počet přidělených objektů se rovná počtu živých objektů, můžeme dojít k závěru, že žádný z těchto objektů nemohl být sebrán. To je v souladu s našimi očekáváními.

Další zajímavý bod: paměť údajně spotřebovává MemoryConsumer Objekty nezahrnují paměť spotřebovanou bajtovými poli. To ukazuje, že náš profilovací nástroj nevystavuje vztahy hierarchického zadržování, ale spíše statistiku třídy po třídě. To je důležité pochopit, když používáte hprof k určení úniku paměti.

Odkud tedy přišla ta děravá bajtová pole? Všimněte si, že MemoryConsumer objekty a stopy odkazů na bajtová pole 1994 a 1995 v následující části Trasování. Hle, aj, tyto stopy nám říkají, že MemoryConsumer objekty byly vytvořeny v MemoryLeak třídy hlavní() metoda a že bajtová pole byla vytvořena v konstruktoru (() metoda ve VM-speak). Zjistili jsme únik paměti, čísla řádků a vše:

TRACE 1994: (vlákno = 1) MemoryLeak.main (MemoryLeak.java:16) TRACE 1995: (vlákno = 1) MemoryConsumer. (MemoryLeak.java:36) MemoryLeak.main (MemoryLeak.java:16) 

Diagnostikujte prase CPU

V seznamu 2, a BusyWork třída má každé vlákno volání metody, která reguluje, jak vlákno funguje, změnou doby spánku mezi záchvaty provádění výpočtů náročných na CPU:

Výpis 2. Program CPUHog

01 / ** Hlavní třída pro kontrolní test. * / 02 public class CPUHog {03 public static void main (String [] args) {04 05 Thread slouch, workingStiff, workaholic; 06 slouch = new Slouch (); 07 workingStiff = nový WorkingStiff (); 08 workoholik = nový workoholik (); 09 10 slouch.start (); 11 workingStiff.start (); 12 workoholik. Start (); 13} 14} 15 16 / ** Nízké využití procesoru. * / 17 třída Slouch rozšiřuje vlákno {18 public Slouch () {19 super ("Slouch"); 20} 21 public void run () {22 BusyWork.slouch (); 23} 24} 25 26 / ** Střední využití procesoru. * / 27 třída WorkingStiff rozšiřuje vlákno {28 public WorkingStiff () {29 super ("WorkingStiff"); 30} 31 public void run () {32 BusyWork.workNormally (); 33} 34} 35 36 / ** Vlákno s vysokým využitím CPU. * / 37 třída Workaholic rozšiřuje vlákno {38 public Workaholic () {39 super ("Workaholic"); 40} 41 public void run () {42 BusyWork.workTillYouDrop (); 43} 44} 45 46 / ** Třída se statickými metodami, která spotřebovává různé množství 47 * času CPU. * / 48 třída BusyWork {49 50 public static int callCount = 0; 51 52 public static void slouch () {53 int SLEEP_INTERVAL = 1000; 54 computeAndSleepLoop (SLEEP_INTERVAL); 55} 56 57 public static void workNormally () {58 int SLEEP_INTERVAL = 100; 59 computeAndSleepLoop (SLEEP_INTERVAL); 60} 61 62 public static void workTillYouDrop () {63 int SLEEP_INTERVAL = 10; 64 computeAndSleepLoop (SLEEP_INTERVAL); 65} 66 67 private static void computeAndSleepLoop (int sleepInterval) {68 int MAX_CALLS = 10 000; 69 while (callCount <MAX_CALLS) {70 computeAndSleep (sleepInterval); 71} 72} 73 74 private static void computeAndSleep (int sleepInterval) {75 int POČÍTAČE = 1000; 76 dvojnásobný výsledek; 77 78 // Vypočítat. 79 callCount ++; 80 pro (int i = 0; i <VÝPOČTY; i ++) {81 result = Math.atan (callCount * Math.random ()); 82} 83 84 // Spánek. 85 try {86 Thread.currentThread (). Sleep (sleepInterval); 87} catch (InterruptedException ie) {88 // Nedělat nic. 89} 90 91} // Ukončit computeAndSleep. 92} // Ukončit BusyWork. 

Existují tři vlákna - Workoholik, WorkingStiff, a Slouch - jejichž pracovní etika se liší řádově, soudě podle práce, kterou se rozhodnou dělat. Prozkoumejte níže uvedenou sekci Vzorky CPU profilu. Tři nejlépe hodnocené stopy ukazují, že CPU strávil většinu času výpočtem náhodných čísel a obloukových tečen, jak bychom očekávali:

ZAČÍNÁ SE VZORKY PROCESORU (celkem = 935) Út 4. září 20:44:49 2001 pořadí vlastní akumulace počet trasovací metoda 1 39,04% 39,04% 365 2040 Java / util / Random.next 2 26,84% 65,88% 251 2042 Java / util / Random. nextDouble 3 10,91% 76,79% 102 2041 java / lang / StrictMath.atan 4 8,13% 84,92% 76 2046 BusyWork.computeAndSleep 5 4,28% 89,20% 40 2050 java / lang / Math.atan 6 3,21% 92,41% 30 2045 java / lang / Math.random 7 2,25% 94,65% 21 2051 java / lang / Math.random 8 1,82% 96,47% 17 2044 java / util / Random.next 9 1,50% 97,97% 14 2043 java / util / Random.nextDouble 10 0,43% 98,40% 4 2047 BusyWork.computeAndSleep 11 0,21% 98,61% 2 2048 java / lang / StrictMath.atan 12 0,11% 98,72% 1 1578 java / io / BufferedReader.readLine 13 0,11% 98,82% 1 2054 java / lang / Thread.sleep 14 0,11% 98,93% 1 1956 java / security / PermissionCollection.setReadOnly 15 15,11% 99,04% 1 2055 java / lang / Thread.sleep 16 0,11% 99,14% 1 1593 java / lang / String.valueOf 17 0,11% 99,25% 1 2052 java / lang / Matematika. Náhoda 18 0,11% 99,36% 1 2049 java / util / Random.nextDouble 19 0,11% 99,47% 1 2031 BusyWork.computeAndSleep 20 0,11% 99,57% 1 1530 slunce / io / CharToByteISO8859_1.convert ... 

Všimněte si, že volání na BusyWork.computeAndSleep () metoda zabírá 8,13 procenta, 0,43 procenta a 0,11 procenta pro Workoholik, WorkingStiff, a Slouch vlákna, resp. Můžeme zjistit, o která vlákna se jedná, prozkoumáním tras, na které se odkazuje ve sloupci trasování sekce CPU Samples výše (pořadí 4, 10 a 19) v následující sekci Trace:

$config[zx-auto] not found$config[zx-overlay] not found