Ú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í čashloubka = 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ákenzávit = y
: Říká hprof, aby identifikoval vlákna ve stopách zásobníkudoe = 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: