Minne lekkasjer og blokkeringer og CPU hogs, oh my! Java-applikasjonsutviklere møter ofte disse kjøretidsproblemene. De kan være spesielt skremmende i et komplekst program med flere tråder som går gjennom hundretusener av kodelinjer - et program du ikke kan sende fordi det vokser i minne, blir inaktivt eller sluker opp flere CPU-sykluser enn det burde.
Det er ingen hemmelighet at Java-profileringsverktøy har hatt en lang vei å innhente sine alternative språk-kolleger. Det finnes nå mange kraftige verktøy som hjelper oss å spore bakmennene bak de vanlige problemene. Men hvordan utvikler du tillit til din evne til å bruke disse verktøyene effektivt? Tross alt bruker du verktøyene til å diagnostisere kompleks atferd du ikke forstår. For å forverre situasjonen din, er dataene fra verktøyene rimelig kompliserte, og informasjonen du ser på eller etter er ikke alltid klar.
Da jeg møtte lignende problemer i min forrige inkarnasjon som eksperimentell fysiker, opprettet jeg kontrolleksperimenter med forutsigbare resultater. Dette hjalp meg med å få tillit til målesystemet jeg brukte i eksperimenter som genererte mindre forutsigbare resultater. Tilsvarende bruker denne artikkelen hprof-profileringsverktøyet for å undersøke tre enkle kontrollapplikasjoner som viser de tre vanlige problematferdene som er oppført ovenfor. Selv om det ikke er like brukervennlig som noen kommersielle verktøy på markedet, er hprof inkludert i Java 2 JDK og kan, som jeg vil demonstrere, effektivt diagnostisere denne oppførselen.
Kjør med hprof
Å kjøre programmet ditt med hprof er enkelt. Bare påkall Java-kjøretiden med følgende kommandolinjealternativ, som beskrevet i JDK-verktøydokumentasjonen for Java-applikasjonsstarteren:
java -Xrunhprof [: help] [: =, ...] MyMainClass
En liste over underalternativer er tilgjengelig med [:hjelp]
alternativet vist. Jeg genererte eksemplene i denne artikkelen ved hjelp av Blackdown-porten på JDK 1.3-RC1 for Linux med følgende startkommando:
java -classic -Xrunhprof: heap = sites, cpu = samples, depth = 10, monitor = y, thread = y, doe = y MemoryLeak
Følgende liste forklarer funksjonen til hvert delalternativ som ble brukt i forrige kommando:
haug = nettsteder
: Forteller hprof å generere stakkspor som indikerer hvor minnet ble tildeltcpu = prøver
: Forteller hprof å bruke statistisk prøvetaking for å bestemme hvor CPU bruker tiden sindybde = 10
: Forteller hprof å vise stakkspor 10 nivåer dype, på det mestemonitor = y
: Forteller hprof å generere informasjon om tvisteskjermer som brukes til å synkronisere arbeidet til flere trådertråd = y
: Forteller hprof å identifisere tråder i stakkspordoe = y
: Forteller hprof å produsere en dump av profildata ved utgang
Hvis du bruker JDK 1.3, må du slå av standard HotSpot-kompilatoren med -klassisk
alternativ. HotSpot har sin egen profil, påkalt gjennom en -Xprof
alternativet, som bruker et annet format enn det jeg skal beskrive her.
Å kjøre programmet ditt med hprof vil etterlate en fil som heter java.hprof.txt
i arbeidskatalogen din; denne filen inneholder profilinformasjonen som samles inn mens programmet kjører. Du kan også generere en dump når som helst mens programmet kjører ved å trykke Ctrl- i Java-konsollvinduet på Unix eller Ctrl-Break på Windows.
Anatomi av en hprof-utdatafil
En hprof-utdatafil inneholder seksjoner som beskriver forskjellige egenskaper ved det profilerte Java-programmet. Det starter med en overskrift som beskriver formatet, som overskriften hevder kan endres uten varsel.
Utgangsfilens tråd- og sporingsseksjoner hjelper deg med å finne ut hvilke tråder som var aktive da programmet kjørte og hva de gjorde. Tråddelen inneholder en liste over alle tråder som er startet og avsluttet i løpet av programmets liv. Trace-delen inneholder en liste over nummererte stakkspor for noen tråder. Disse stabelsporingsnumrene er krysshenvist i andre filseksjoner.
Seksjonene Heap Dump og Sites hjelper deg med å analysere minnebruk. Avhengig av haug
undervalg du velger når du starter den virtuelle maskinen (VM), kan du få en dump av alle levende objekter i Java-bunken (haug = dump
) og / eller en sortert liste over tildelingssteder som identifiserer de mest tildelte objektene (haug = nettsteder
).
Seksjonene CPU-prøver og CPU-tid hjelper deg med å forstå CPU-bruken; delen du får avhenger av din prosessor
delvalg (cpu = prøver
eller cpu = tid
). CPU-prøver gir en statistisk kjøringsprofil. CPU-tid inkluderer målinger av hvor mange ganger en gitt metode ble kalt og hvor lang tid hver metode tok å utføre.
Avsnittene Monitor Time og Monitor Dump hjelper deg å forstå hvordan synkronisering påvirker programmets ytelse. Monitor Time viser hvor mye tid trådene dine opplever strid om låste ressurser. Monitor Dump er et øyeblikksbilde av skjermer som er i bruk. Som du ser, er Monitor Dump nyttig for å finne fastlåser.
Diagnostiser en minnelekkasje
I Java definerer jeg en minnelekkasje som en (vanligvis) utilsiktet svikt i å henvise forkastede objekter, slik at søppeloppsamleren ikke kan gjenvinne minnet de bruker. De Hukommelsestap
programmet i Listing 1 er enkelt:
Oppføring 1. MemoryLeak-program
01 import java.util.Vector; 02 03 offentlig klasse MemoryLeak {04 05 public static void main (String [] args) {06 07 int MAX_CONSUMERS = 10000; 08 int SLEEP_BETWEEN_ALLOCS = 5; 09 10 ConsumerContainer objectHolder = ny ConsumerContainer (); 11 12 mens (objectHolder.size () <MAX_CONSUMERS) {13 System.out.println ("Allocating object" + 14 Integer.toString (objectHolder.size ()) 15); 16 objectHolder.add (ny MemoryConsumer ()); 17 prøv {18 Thread.currentThread (). Sleep (SLEEP_BETWEEN_ALLOCS); 19} fange (InterruptedException ie) {20 // Gjør ingenting. 21} 22} // mens. 23} // hoved. 24 25} // Slutt på MemoryLeak. 26 27 / ** Navngitt containerklasse for å holde objektreferanser. * / 28 klasse ConsumerContainer utvider Vector {} 29 30 / ** Klasse som bruker en fast mengde minne. * / 31 klasse MemoryConsumer {32 offentlig statisk slutt int MEMORY_BLOCK = 1024; 33 offentlig byte [] minneHoldingArray; 34 35 MemoryConsumer () {36 memoryHoldingArray = ny byte [MEMORY_BLOCK]; 37} 38} // Avslutt MemoryConsumer.
Når programmet kjører, skaper det et ConsumerContainer
objekt, og begynner deretter å lage og legge til MemoryConsumer
objekter minst 1 KB i størrelse til det ConsumerContainer
gjenstand. Å holde gjenstandene tilgjengelige gjør dem utilgjengelige for søppeloppsamling, og simulerer en minnelekkasje.
La oss se på utvalgte deler av profilfilen. Nettstedsseksjonens første linjer viser tydelig hva som skjer:
SITES BEGIN (ordered by live bytes) Mon Sep 3 19:16:29 2001 percent live alloceded stack class rank self accum bytes objs bytes objs trace name 1 97.31% 97.31% 10280000 10000 10280000 10000 1995 [B 2 0.39% 97.69% 40964 1 81880 10 1996 [L; 3 0,38% 98,07% 40000 10000 40000 10000 1994 MemoryConsumer 4 0,16% 98,23% 16388 1 16388 1 1295 [C 5 0,16% 98,38% 16388 1 16388 1 1304 [C ...
Det er 10 000 gjenstander av typen byte []
([B
i VM-speak) samt 10.000 MemoryConsumer
gjenstander. Byte-arrayene tar opp 10 280 000 byte, så det er tilsynelatende overhead like over de rå bytene som hver matrise bruker. Siden antall tildelte objekter er lik antall levende gjenstander, kan vi konkludere med at ingen av disse gjenstandene kan samles opp søppel. Dette er i samsvar med våre forventninger.
Et annet interessant poeng: minnet rapportert å bli fortært av MemoryConsumer
objekter inkluderer ikke minnet som forbrukes av byte-arrayene. Dette viser at profileringsverktøyet vårt ikke avslører hierarkiske inneslutningsforhold, men snarere klasse-for-klasse-statistikk. Dette er viktig å forstå når du bruker hprof for å finne en minnelekkasje.
Hvor kom de utette byte-matriser fra? Legg merke til at MemoryConsumer
objekter og byte-matriser referansespor 1994
og 1995
i den følgende sporingsseksjonen. Se, disse sporene forteller oss at MemoryConsumer
gjenstander ble opprettet i Hukommelsestap
klassen hoved()
metode og at byte-matriser ble opprettet i konstruktøren (()
metode i VM-speak). Vi har funnet minnelekkasjen, linjenumrene og alt:
TRACE 1994: (thread = 1) MemoryLeak.main (MemoryLeak.java:16) TRACE 1995: (thread = 1) MemoryConsumer. (MemoryLeak.java:36) MemoryLeak.main (MemoryLeak.java:16)
Diagnostiser en CPU hog
I oppføring 2, a BusyWork
klasse har hver trådkall en metode som regulerer hvor mye tråden fungerer ved å variere hviletiden mellom anfallene for å utføre CPU-intensive beregninger:
Oppføring 2. CPUHog-program
01 / ** Hovedklasse for kontrolltest. * / 02 public class CPUHog {03 public static void main (String [] args) {04 05 Thread slouch, workingStiff, workaholic; 06 slouch = ny Slouch (); 07 workingStiff = ny WorkingStiff (); 08 arbeidsnarkoman = ny Workaholic (); 09 10 slouch.start (); 11 workingStiff.start (); 12 arbeidsnarkoman.start (); 13} 14} 15 16 / ** Lav CPU-brukstråd. * / 17 klasse Slouch utvider tråd {18 public Slouch () {19 super ("Slouch"); 20} 21 offentlig tomkjøring () {22 BusyWork.slouch (); 23} 24} 25 26 / ** Middels CPU-brukstråd. * / 27 klasse WorkingStiff utvider tråd {28 offentlig WorkingStiff () {29 super ("WorkingStiff"); 30} 31 public void run () {32 BusyWork.workNormally (); 33} 34} 35 36 / ** Høy CPU-brukstråd. * / 37 klasse Workaholic extends Thread {38 public Workaholic () {39 super ("Workaholic"); 40} 41 ugyldig offentlig kjøring () {42 BusyWork.workTillYouDrop (); 43} 44} 45 46 / ** Klasse med statiske metoder for å forbruke varierende mengder 47 * av CPU-tid. * / 48 klasse BusyWork {49 50 offentlig statisk int callCount = 0; 51 52 offentlig statisk ugyldig slouch () {53 int SLEEP_INTERVAL = 1000; 54 computeAndSleepLoop (SLEEP_INTERVAL); 55} 56 57 offentlig statisk tomrom fungerer Normalt () {58 int SLEEP_INTERVAL = 100; 59 computeAndSleepLoop (SLEEP_INTERVAL); 60} 61 62 offentlig statisk ugyldig workTillYouDrop () {63 int SLEEP_INTERVAL = 10; 64 computeAndSleepLoop (SLEEP_INTERVAL); 65} 66 67 privat statisk tomrom computeAndSleepLoop (int sleepInterval) {68 int MAX_CALLS = 10000; 69 mens (callCount <MAX_CALLS) {70 computeAndSleep (sleepInterval); 71} 72} 73 74 privat statisk tomrom computeAndSleep (int sleepInterval) {75 int COMPUTATIONS = 1000; 76 dobbelt resultat; 77 78 // Beregn. 79 callCount ++; 80 for (int i = 0; i <COMPUTATIONS; i ++) {81 result = Math.atan (callCount * Math.random ()); 82} 83 84 // Søvn. 85 prøv {86 Thread.currentThread (). Sleep (sleepInterval); 87} fangst (InterruptedException ie) {88 // Gjør ingenting. 89} 90 91} // End computeAndSleep. 92} // Slutt BusyWork.
Det er tre tråder - Arbeidsnarkoman
, WorkingStiff
, og Slouch
- hvis arbeidsmoral varierer etter størrelsesorden, ut fra arbeidet de velger å gjøre. Undersøk profilens CPU-prøver, vist nedenfor. De tre høyest rangerte sporene viser at CPUen brukte mesteparten av tiden på å beregne tilfeldige tall og buetangenser, som vi forventer:
CPU-PRØVER BEGYNNER (totalt = 935) Tirsdag 4. september 20:44:49 2001 rang selvmåling sporingsmetode 1 39,04% 39,04% 365 2040 java / util / Tilfeldig.neste 2 26,84% 65,88% 251 2042 java / util / Tilfeldig. nesteDobbelt 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.next Dobbelt 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 / Tråd. Søvn 14 0,11% 98,93% 1 1956 java / security / PermissionCollection.setReadOnly 15 0,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 / Matematikk tilfeldig 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 sun / io / CharToByteISO8859_1.convert ...
Merk at samtaler til BusyWork.computeAndSleep ()
metoden tar opp 8,13 prosent, 0,43 prosent og 0,11 prosent for Arbeidsnarkoman
, WorkingStiff
, og Slouch
tråder, henholdsvis. Vi kan se hvilke tråder dette er ved å undersøke sporene det er referert til i CPU-sampleseksjonens sporingskolonne ovenfor (rangering 4, 10 og 19) i følgende sporingsdel: