Det er afgørende for, om din Java-applikation bliver en succes, at du kan uddrage nyttige oplysninger fra dine logfiler. Logdata giver dig værdifuld indsigt i din applikations ydeevne, stabilitet og brugervenlighed.
Analyse af logdata kan virke kedelig, men det behøver det ikke at være. Der findes en række værktøjer til læsning, parsing og konsolidering af logdata. Grundlæggende kommandolinjeværktøjer som grep, uniq og sort kan kombinere og udtrække nyttige oplysninger fra logfiler. Mere avancerede logparsere som Logstash eller Fluentd kan udtrække nøgledata fra dine logfiler til let søgbare tokens. Cloud-baserede logningstjenester som SolarWinds® Loggly® gemmer dine logdata for dig og tilbyder sofistikerede analysefunktioner, hvilket eliminerer behovet for selv at vedligeholde logfiler.
Dette afsnit udforsker nogle metoder og værktøjer til analyse af logfiler med det formål at forbedre dine applikationer.
- Find de mest almindelige undtagelser
- Finding Exceptions by Type Using Grep
- Findelse af undtagelser efter klasse ved hjælp af grep
- Anvendelse af en loghåndteringsløsning
- Debugging af produktionsproblemer
- Saml oplysninger om problemet
- Identificer årsagen til problemet
- Løs problemet og undgå, at det gentager sig
- Mere værktøjer til debugging i produktionen
- jdb
- OverOps
- BTrace
- Chronon
- jhsdb
- Sporing af transaktioner
- Sporing af unikke ID’er
- Sporing af metodekald
- Sporingsmetoder i Log4j
- Sporingsmetoder i SLF4J
- Håndtering af hukommelsesforbrug
- Garbage Collection
- Hukommelseslækager
- Supplerende ressourcer
- Se det. Analyser det. Inspicer det. Løs det
Find de mest almindelige undtagelser
Find de mest almindelige undtagelser kan hjælpe dig med at lokalisere områder med dårlig ydeevne i din Java-applikation. De fleste logningsrammer registrerer typen af undtagelse, undtagelsesmeddelelsen og den metode, hvor undtagelsen opstod. Hvis du bruger Log4j, vil en log over undtagelser se ud som en af følgende.
09:54:44.565 ERROR Log4jTest.MathClass - java.lang.ArithmeticException: / by zero10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: myFile (No such file or directory)10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: newFile (No such file or directory)
Hvis dit output indeholder stakspor, skal du se afsnittet Parsing Multiline Stack Traces i denne vejledning.
Lad os starte med et simpelt eksempel ved hjælp af det populære GNU-værktøj grep. Derefter viser vi, hvordan et loghåndteringsværktøj kan gøre det endnu nemmere.
Finding Exceptions by Type Using Grep
Den følgende Unix-kommando finder undtagelser, uddrager undtagelsestypen og tæller antallet af forekomster. grep er et populært kommandolinjeværktøj, der udfører mønstermatchning, mens uniq og sort henholdsvis grupperer og sorterer resultaterne:
$ grep -o "\w*Exception" myLog.log | sort -r | uniq -c
I dette eksempel passer det regulære udtryk \w*Exception
til ethvert ord, der slutter med “Exception”. Flaget -o
fortæller grep, at det kun skal udskrive de dele af output, der matcher søgestrengen. sort -r
sorterer resultatet i omvendt rækkefølge, mens uniq -c
grupperer resultaterne og tæller antallet af forekomster. Resultatet er, at vi ender med en optælling af undtagelser efter type.
2 FileNotFoundException1 ArithmeticException
Vi kan også bruge grep til at søge i loggen efter hvert enkelt tilfælde af disse undtagelser.
$ grep ArithmeticException myLog.log09:54:44.565 ERROR Log4jTest.Log4jTest - java.lang.ArithmeticException: / by zero
Dette returnerer hver linje, der indeholder en instans af søgestrengen ArithmeticException
, med selve søgestrengen fremhævet i output.
Findelse af undtagelser efter klasse ved hjælp af grep
Du kan også søge efter undtagelser efter den klasse, hvori de opstod. For loghændelser med en enkelt linje grupperer følgende kommando antallet af undtagelser efter klasse og type. grep udtrækker klassens navn ved at søge efter et ord efterfulgt af et bestemt tegn (i dette eksempel bruges en bindestreg, selv om ethvert tegn kan bruges, så længe det er unikt for posten). Selv om dette tegn ikke er afgørende, hjælper det os med at finde klassens navn i logbegivenheden. Med OR-operatoren (angivet med |) vil grep også udtrække navnet på undtagelsen ved at søge efter et ord, der indeholder strengen “Exception.”
sed er et andet kommandolinjeværktøj, der kan bruges til at formatere output fra grep. I dette eksempel fjerner sed specialtegnet fra vores output samt eventuelle new-line-tegn. Outputtet ledes derefter til kommandoerne uniq og sort for henholdsvis at gruppere og sortere resultaterne.
$ grep -o "w* -|\w*Exception" myLog.log | sed 'N; s/ -n/ /' | sort -r | uniq -c
Resultatet bringer os tættere på de vigtigste problemområder:
2 Log4jTest FileNotFoundException1 MathClass ArithmeticException
Med disse resultater kan vi bruge grep til at finde flere detaljer om problemer, der forekommer i bestemte klasser. Følgende kommando henter f.eks. undtagelser, der er opstået i MathClass
-klassen.
$ grep -e "MathClass.*Exception" myLog.log09:54:44.565 ERROR Log4jtest.MathClass - java.lang.ArithmeticException: / by zero
Anvendelse af en loghåndteringsløsning
De fleste loghåndteringsløsninger tilbyder måder at gruppere og søge logposter på baseret på logtype, beskedindhold, klasse, metode og tråd. Hvis dine logfiler allerede bliver analyseret og lagret, kan mange løsninger grafisk opstille og sortere undtagelser efter antallet af forekomster. Dette er en peg og klik-operation i Loggly, så du behøver ikke at lære komplicerede grep-kommandoer udenad. Loggly indekserer også logoprotokollerne, hvilket gør søgninger og optællinger meget hurtigere end grep. Vi kan f.eks. bruge feltudforskeren til at se en liste over undtagelser og deres hyppighed og derefter klikke på en undtagelse for at få vist alle relevante logfiler.
Anvendelse af Loggly-feltudforskeren til hurtigt at finde logfiler efter undtagelsestype.
Loggly tilbyder også visualiseringsværktøjer, som kan være mere informative end det tekstbaserede output fra grep. Grafer som denne kan hjælpe dig med at prioritere ressourcer til udviklingssprints eller til patches efter en ny udgivelse.
Grafik over undtagelser efter hyppighed i Loggly.
Debugging af produktionsproblemer
Når det drejer sig om produktionsproblemer, er det vigtigt med tid. En kritisk fejl i dit program vil ikke blot gøre dine brugere utilfredse; det vil også sænke salget og mindske tilliden til dit program eller din tjeneste.
I de fleste tilfælde kan løsningen af et problem opdeles i tre vigtige trin:
- Saml oplysninger om problemet
- Identificer årsagen til problemet
- Find en løsning og undgå, at problemet gentager sig
Saml oplysninger om problemet
Det første trin er at indsamle oplysninger om problemet. Indsaml så mange oplysninger som muligt – skærmbilleder, nedbrudsrapporter, logfiler, links (for webtjenester) osv. – for at hjælpe med at indsnævre de potentielle årsager. Du vil gerne have den bruger, der oplevede problemet, til at give detaljerede oplysninger om hændelsen, herunder: hvornår og hvor i programmet problemet opstod, hans eller hendes handlinger, der førte op til problemet, driftsmiljøet og enhver mærkelig eller usædvanlig adfærd fra programmet før og efter problemet opstod.
Derfra kan du begynde at indsamle logoplysninger. Hvis dit program er en hosted service, skal du begynde at hente logfiler fra web- og applikationsserverne. Hvis dit program er en distribueret softwarepakke, skal du bede brugeren om at inkludere logdata i sin fejlrapport. Alternativt, hvis dit program sender loghændelser til en centraliseret logserver, vil dine logfiler være umiddelbart tilgængelige.
Når du har en rimelig mængde data vedrørende problemet, kan du begynde at spore det i koden.
Identificer årsagen til problemet
Når du har indsamlet oplysninger om problemet, er det næste skridt at identificere årsagen til det. Reproduktion af en fejl i et udviklingsmiljø er en af de nemmeste måder at bekræfte dens eksistens på, men det kan være tidskrævende og virker måske ikke i alle tilfælde. Hvis du har et grundigt sæt logfiler, kan du gå direkte til kilden til problemet og spare tid og frustration.
En fejlrapport giver dig en generel idé om, hvad problemet er, og hvor det er opstået. Ved hjælp af dit foretrukne loghåndteringsværktøj kan du indsnævre din søgning til et mindre udvalg af logposter ved at søge efter et unikt datatoken, f.eks. et brugernavn, et sessions-id eller en beskedtekst.
Lad os gennemgå et eksempelscenarie for at demonstrere, hvordan du debugger et system. Forestil dig, at vi har en webbaseret grænseflade til fjernlogning på et websted. Websiden har en login-skærm, der udfører grundlæggende autentificering ved hjælp af et brugernavn og en adgangskode. Brugerne har rapporteret, at de ikke kan logge ind på webstedet. Websiden accepterer deres input, men fejler derefter med en generisk fejl.
Eksempel på et websted, der rapporterer en fejl efter et loginforsøg.
Denne meddelelse giver os ikke mange oplysninger ud over en generisk angivelse af loggens sværhedsgrad. Søgning i en logmanager efter poster med “Severe” i niveauet eller meddelelsen kan resultere i hundredvis af resultater uden nogen garanti for, at nogen af dem er relateret til det pågældende problem. Heldigvis har vores Logger
også registreret brugernavnet på den bruger, der oplevede fejlen, så vi kan filtrere på brugernavnet “admin”.
Visning af en Java-undtagelse i Loggly.
Hvis vi borer ned, kan vi se, at årsagen til fejlen er en manglende eller ugyldig tabel. Dette er et alvorligt problem, da det kan indikere en utilsigtet sletning eller databasekorruption.
ERROR: Exception for user admincom.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test_schema.users' doesn't exist...com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1651) at TestApplication.Test.doPost(Test.java:33) at javax.servlet.http.HttpServlet.service(HttpServlet.java:646) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at...
Gennemgår vi staksporet, kan vi se, at tjenesten mislykkedes på linje 33 i Test.java. Denne linje består af en SQL-anvisning, der henter data om en bruger fra tabellen test_schema.users.
rs = stmt.executeQuery("SELECT * FROM test_schema.users WHERE username = " + username + " AND Password = " + password);
Når vi forsøger at køre denne forespørgsel i en SQL-front-end, finder vi ud af, at tabellen “test_schema.users” ikke findes. Databasen har dog en “test_schema.user”-tabel. På et tidspunkt kan en udvikler ved en fejl have indtastet det forkerte tabelnavn og skubbet den reviderede formular til produktionen. Nu ved vi, hvad problemet er, og hvor det forekommer i vores kode.
Med SolarWinds Loggly kan du fejlfinde og finde den grundlæggende årsag til problemer ved at spore fejl gennem din applikations stak, på tværs af flere logbegivenheder og endda lokalisere den relevante kodelinje i GitHub.
Løs problemet og undgå, at det gentager sig
Nu, hvor vi har identificeret problemet og dets årsag, er det sidste skridt at løse det. Vores login-eksempel var et overdrevet tilfælde med en forholdsvis nem løsning, men du kan støde på mere komplicerede fejl, der har rod i forskellige områder af din applikation. Før du kaster dig ud i den hurtige og beskidte løsning, skal du nøje overveje, hvordan din ændring vil påvirke applikationen. Er dette virkelig den bedste løsning på problemet? Er det muligt, at din ændring vil interferere med en anden komponent? Vil denne rettelse gøre det vanskeligt at indføre nye rettelser eller funktioner senere hen? Vil denne rettelse også forhindre lignende problemer i at dukke op senere?
For eksempel, hvad nu hvis det lykkedes to forskellige brugere at oprette to separate konti med det samme brugernavn og password? Du kunne gennemtvinge et unikt brugernavn for alle brugere, men hvordan ville det påvirke din databasestruktur, din autentificeringskode og din eksisterende brugerbase? Du kunne tilføje et nyt unikt felt, f.eks. en e-mailadresse, og gøre det obligatorisk, men hvad nu hvis nogle af dine nuværende brugere ikke har oplyst e-mailadresser? Hvad hvis nogle e-mail-adresser er delt på tværs af flere konti? Hvordan vil disse nye regler påvirke dine registrerings-, søge- og administrationssider?
Du skal også overveje forsinkelsen mellem oprettelsen af en rettelse og implementeringen af den. I et virksomhedsmiljø skal din kode gennemgås af andre udviklere, integreres i kodebasen, opbygges, testes af QA, stages og måske gennemgå flere yderligere trin, før den kommer i produktion. Din virksomhed har måske særlige protokoller for kritiske eller tidsfølsomme fejl, som du skal følge. Når du har fundet en løsning, skal du medtage din løsning i din dokumentation samt alle rimelige løsninger på problemet. På denne måde kan dine kunder stadig bruge dit produkt, og dit supportteam kan reducere antallet af dublerede fejlrapporter, mens rettelsen er på vej til produktion.
Når du har implementeret rettelsen, skal du fortsætte med at overvåge dit produktionsprogram for at kontrollere, at problemet er blevet løst. Efter at have implementeret rettelsen til databaseproblemet i ovenstående eksempel bør vi f.eks. ikke længere se hændelser med “Table ‘test_schema.users’ doesn’t exist” (tabellen ‘test_schema.users’ eksisterer ikke). Hvis fejlene fortsætter, eller hvis vi begynder at se nye fejl, ved vi, at rettelsen ikke virkede. Ideelt set vil vi se et mønster som vist i følgende skærmbillede, hvor antallet af fejl falder til nul umiddelbart efter, at vi har implementeret patchen.
Kortlægning af antallet af logfiler, der indeholder en fejl i Loggly.
Mere værktøjer til debugging i produktionen
Selv om logning er den gennemprøvede metode til fejlfinding og debugging af programmer, kan disse værktøjer hjælpe dig med at få mere indsigt i, hvordan dit program fungerer.
jdb
jdb, Java Debugger, er et kommandolinjeværktøj til debugging af Java-klasser. jdb er let at bruge og leveres sammen med Java Development Kit. Ved at bruge jdb oprettes en ny Java Virtual Machine (JVM), så du kan debugge en klasse uden at påvirke nogen kørende programmer. Du kan også bruge jdb til at debugge kørende programmer ved at tilføje følgende parametre til din java-kommando:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n
Når JVM’en starter, tildeler den et portnummer til indgående jdb-forbindelser. Du kan derefter oprette forbindelse til den kørende JVM-instans ved hjælp af jdb -attach:
$ jdb -attach
Du kan f.eks. bruge dette til at oprette forbindelse mellem din debugger og en kørende produktionsinstans. Vær forsigtig med at bruge breakpoints i dette scenario, da det kan sætte en aktiv tråd på pause. Dette kan have konsekvenser, hvis f.eks. en kunde bruger programmet, mens du debugger det. Du kan finde flere oplysninger i Java-dokumentationen om jdb.
OverOps
OverOps er en pakke af værktøjer til overvågning af programmer, analyse af kode og opdagelse af problemer. I modsætning til logføringssoftware, som er afhængig af output, der genereres af et kørende program, opretter OverOps direkte forbindelse til den virtuelle Java-maskine for at kortlægge programmets kodebase, læse variabler og registrere fejl. Dette gør det muligt at opfange flere fejl og registrere flere data end selv applikationens logningsramme. OverOps anvender en software as a service-model (SaaS), hvor målinger indsamles og gemmes på OverOps’ cloud-servere. Du kan dog også implementere det on-premises. I begge tilfælde kan du se dine data ved hjælp af en webbaseret grænseflade.
BTrace
BTrace er et sporingsværktøj, der giver dig mulighed for at overvåge alle aspekter af dit program, fra klassebetegnelser til fejl. BTrace anvender en aspektorienteret programmeringstilgang, der indebærer brug af annotationer, som angiver, hvor og hvordan BTrace overvåger din applikation. Følgende BTrace-script overvåger og logger f.eks. hvert eneste kald til javax.swing
-pakken.
import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*; @BTrace public class AllMethods { @OnMethod( clazz="/javax\.swing\..*/", method="/.*/" ) public static void m(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { print(Strings.strcat("entered ", probeClass)); println(Strings.strcat(".", probeMethod)); }}
For flere oplysninger om BTrace kan du se BTrace GitHub-repositoriet og Wiki.
Chronon
Chrononon giver dig mulighed for at spole tilbage og afspille hele eksekveringsflowet for et program. Den registrerer hver enkelt ændring, der foretages i løbet af et programs levetid, så du kan reproducere programmets tilstand på et hvilket som helst tidspunkt. Optagelser gemmes i en fil, hvilket gør det nemt at overføre eksekveringshistorikken fra en produktionsmaskine til en udviklingsmaskine til testning.
Chronon består af Chronon Recording Server, som giver dig mulighed for at optage Java-programmer eksternt; Embedded Chronon, som indlejrer optageren i et program; og Time Travelling Debugger, som giver dig mulighed for at afspille optagelser igen.
jhsdb
jhsdb (Java HotSpot Debugger) er en pakke af værktøjer til fejlsøgning, analyse og profilering af standard-JVM’en, der leveres med både OpenJDK og Oracle JDK. jhsdb giver dig mulighed for at knytte dig til kørende Java-processer, tage snapshots af stack traces og endda analysere nedbrudte JVM’er. Du kan bruge den til at få adgang til heap, code cache, garbage collection-statistik og meget mere. Du kan få mere at vide på dokumentationssiden jhsdb.
Sporing af transaktioner
Når et problem opstår, er det vigtigt at vide, hvor problemet startede, og hvordan det påvirkede resten af dit program. Dette er vanskeligt nok i et monolitisk program, men bliver endnu vanskeligere i en distribueret serviceorienteret arkitektur, hvor en enkelt anmodning kan ramme snesevis af tjenester. Det er ikke altid indlysende, hvilken tjeneste der indeholder den grundlæggende årsag til fejlen, eller hvordan den påvirkede andre tjenester. Sporing giver de data, der er nødvendige for at følge eksekveringsstien for din applikation og bore ned til den nøjagtige årsag til et problem.
I afsnittet Fejlfinding af produktionsproblemer i vejledningen gennemgik vi et eksempel, hvor en bruger havde problemer med at logge ind på en webapplikation på grund af en ugyldig databasetabel. I dette afsnit viser vi, hvordan sporing af transaktioner spillede en central rolle i løsningen af problemet.
Sporing af unikke ID’er
For at kunne spore en sekvens af loghændelser skal du have en måde at identificere relaterede logfiler på entydigt. Et miljø med flere brugere kan generere hundredvis af identiske logfiler, hvilket gør det vanskeligt at søge på baggrund af tidsstempel eller Logger
. En nemmere løsning er at inkludere en unik identifikator med relaterede logposter. Denne identifikator kan være et brugernavn, et sessions-ID, en API-nøgle eller en Universally Unique Identifier (UUID). Heldigvis er ThreadContext perfekt egnet til denne opgave.
I eksemplet Debugging Production Problems havde vi en webbaseret brugergrænseflade, der blev hostet i en Tomcat-servlet, som havde forbindelse til en MySQL-database. Brugerne indtastede deres legitimationsoplysninger på websiden, og efter at de havde trykket på submit, kørte servletten en forespørgsel, der sammenlignede deres legitimationsoplysninger med dem, der var gemt i databasen. Hvis det lykkedes at autentificere brugeren, blev de omdirigeret til hovedsiden. Hvis der opstod en fejl, blev der logget detaljer om fejlen, og brugerne fik en generisk meddelelse.
Vi kunne fejlfinde dette problem ved at medtage brugernes brugernavne i logmeddelelsen. Dette gjorde det muligt for os at søge hurtigt efter logbegivenheder, der var relateret til admin-brugeren. Ved at bygge videre på det samme eksempel kan vi bruge ThreadContext.put()
til at mappe et brugernavn til et Logger
. Når brugerne indsender deres legitimationsoplysninger, går servletten ind i doPost()
-metoden, som tilføjer brugernes brugernavne til ThreadContext:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {ThreadContext.put("username", request.getParameter("username"));logger.info("Entering doPost().");...}
Anvendelse af Log4j med mønsteret %p : %m%n
resulterer i følgende post:
INFO : Entering doPost().
Vi kan tilføje lignende hændelser for at få adgang til MySQL-databasen, forlade doPost-metoden og udføre andre handlinger. På denne måde ved vi, hvis brugeren udløser en undtagelse, præcis, hvad brugeren gjorde, da undtagelsen skete.
Sporing af metodekald
Mange logningsrammer tilbyder native metoder til sporing af et programs eksekveringssti. Disse metoder varierer en smule fra rammeværk til rammeværk, men følger det samme generelle format.
-
traceEntry()
markerer begyndelsen af en metode. -
traceExit()
markerer slutningen af en metode. For metoder, der returnerer et objekt, kan du samtidig returnere objektet og logge hændelsen medreturn logger.exit(object)
. -
throwing()
markerer en undtagelse, der sandsynligvis ikke vil blive håndteret, f.eks. enRuntimeException
. -
catching()
markerer en undtagelse, der ikke vil blive kastet igen.
Du kan finde rammespecifikke oplysninger om disse metoder i Logger-dokumentationen for Log4j, Logback og java.util.logging
.
Sporingsmetoder i Log4j
Som et eksempel på anvendelse af Log4j erstatter vi Logger.info()-metoderne i vores servlet med sporingsmetoder.
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { logger.entry(); ... logger.exit();}
Vi ændrer også Appender's PatternLayout
til at vise klassens navn, metode og linjenummer (konverteringsmønstre %class
, %M
og %line
).
<PatternLayout pattern="%p %class %M %line: %m%n" />
Sporingsmetoder logger begivenheder på TRACE
-niveauet, hvilket betyder, at vi skal ændre Logger's
-niveauet fra DEBUG
til TRACE
. Ellers vil logmeddelelserne blive undertrykt.
<Loggers> <Root level="trace"> <AppenderRef ref="Console"/> </Root></Loggers> TRACE DatabaseApplication.Login doPost 26: entry...TRACE DatabaseApplication.Login doPost 59: exit
Sporingsmetoder giver også deres egne Markers
. F.eks. viser Logger.entry()
– og Logger.exit()
-metoderne henholdsvis ENTER
og EXIT
.
ENTER 14:47:41.074 TRACE DatabaseApplication.Login - EnterEXIT 14:47:41.251 TRACE DatabaseApplication.Login - Exit
Sporingsmetoder i SLF4J
SLF4J-brugere kan drage fordel af MDC til at spore loghændelser. I lighed med Log4j kan MDC-værdier bruges med en Appender ved hjælp af %X-konverteringsmønsteret.
SLF4J indeholder også entry()
, exit()
, throwing()
og catching()
-metoder via XLogger
-klassen (Extended Logger). Du kan oprette en XLogger
-instans ved hjælp af XLoggerFactory.getXLogger()
.
package DatabaseApplication;import org.slf4j.Logger;import org.slf4j.LoggerFactory; import org.slf4j.ext.XLogger;import org.slf4j.ext.XLoggerFactory; public class Login extends HTTPServlet { final static XLogger xlogger = XLoggerFactory.getXLogger(Login.class.getName()); final static Logger logger = LoggerFactory.getLogger(Login.class.getName()); @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { xlogger.entry(); .. xlogger.exit(); }}
Føj konverteringsmønstrene %class og %line til logback.xml.
<configuration> <appender name="Console" class="ch.qos.Logback.core.ConsoleAppender"> <encoder> <pattern>%-5level %class{36} %M %L: %msg%xEx%n</pattern> </encoder> </appender> <root level="trace"> ... </root></configuration>
De resulterende logposter er identiske med dem, der oprettes af Log4j.
Håndtering af hukommelsesforbrug
Hukommelseshåndtering overses ofte i sprog på højere niveau, f.eks. Java. Selv om den gennemsnitlige mængde hukommelse i moderne enheder er stigende, kan et højt hukommelsesforbrug have en stor indvirkning på din applikations stabilitet og ydeevne. Når Java Virtual Machine ikke længere kan allokere hukommelse fra operativsystemet, kan dit program blive afbrudt og gå ned. Hvis du ved, hvordan du skal administrere hukommelsen, kan du undgå problemer, når dit program vokser i størrelse.
Forestil dig for eksempel, at vi ønsker at gemme et sæt tal, der starter fra et. En bruger angiver en øvre grænse, og programmet gemmer hvert heltal fra “1” til denne grænse. Vi bruger en while-loop med en tæller til at tilføje hvert tal til et array.
import java.util.Scanner;...System.out.print("Please enter the maximum size of the array: ");Scanner scanner = new Scanner(System.in);int limit = scanner.nextInt(); ArrayList intArray = new ArrayList(); int count = 1;while (count <= limit) { intArray.add(count);}
Du har måske bemærket, at count
ikke øges i loopet. Dette er et stort problem; hvis brugeren indtaster et tal, der er større end nul, vil arrayet fortsætte med at vokse, indtil JVM’en bruger al den tilgængelige hukommelse, og programmet går ned.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:2245)at java.util.Arrays.copyOf(Arrays.java:2219)at java.util.ArrayList.grow(ArrayList.java:242)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)at java.util.ArrayList.add(ArrayList.java:440)at MemoryTest.main(MemoryTest.java:10)
Garbage Collection
For at reducere hukommelsesforbruget udfører Java Virtual Machine en periodisk oprydningsproces, der kaldes garbage collection. Garbage collection opsøger blokke af hukommelse, der ikke længere er i brug, og gør dem tilgængelige til genbrug. De følgende ressourcer forklarer Javas garbage collection-proces mere detaljeret.
- Java Garbage Collection Basics (Oracle)
- HotSpot Virtual Machine Garbage Collection Tuning Guide (Oracle)
- Understanding G1 GC Logs (Poonam Bajaj)
- Garbage Collection Tuning Guide (Atlassian)
Garbage Collector genererer diagnostiske oplysninger, som kan være nyttige til profilering af programmer med hensyn til ydeevne. Du kan logge disse oplysninger ved at sende -Xlog:gc til JVM’en, når du starter dit program. Efter hver kørsel udskriver garbage collector statistikker om garbage collection-processen i Unified JVM Logging-formatet. Her er et eksempel:
Using G1 Periodic GC disabled GC(0) Pause Young (Normal) (G1 Evacuation Pause) 7M->1M(64M) 8.450ms
Using G1
fortæller, hvilken garbage collection-metode der anvendes. Garbage-First (G1)-opsamleren er ofte aktiveret som standard på computere med flere processorer og store mængder RAM. Periodic GC disabled
angiver, at garbage collection-processen ikke vil blive gentaget. Den tredje linje fortæller os, at der er tale om en Evakueringspause, hvor objekter kopieres mellem hukommelsesregioner på baggrund af, om de stadig er i brug. Pause Young
fortæller os, at processen rensede den unge generation, som er der, hvor nye objekter allokeres. Som følge heraf faldt det samlede hukommelsesforbrug for objekter i den unge generation fra 7M til 1M ud af 64M allokeret, og processen tog 8.450ms
.
Med garbage collector, der udskriver diagnostiske oplysninger til konsollen, kan vi se, hvordan mængden af allokeret hukommelse (heapstørrelsen) fortsætter med at vokse over tid. Vi fjerner CPU-tiderne for nemheds skyld.
$ java -Xlog:gc MemoryLeakDemo Using G1 Periodic GC disabled GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 25M->23M(64M) 4.372ms GC(1) Concurrent Cycle GC(1) Pause Remark 50M->34M(64M) 0.250ms GC(1) Pause Cleanup 94M->94M(124M) 0.057ms GC(1) Concurrent Cycle 169.695ms GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 94M->94M(124M) 3.738ms GC(3) Concurrent Cycle...
Diagram, der viser en gradvis stigning i JVM-heap-anvendelsen over tid.
Du kan tilføje tidsstempler til hver post med -XX:+PrintGCDateStamps
og -XX:+PrintGCTimeStamps
. Du kan også logge garbage collectorens output til en fil ved at sende-Xlog:gc:file
: til JVM’en, når du starter dit program.
Hvis du overvåger dine logfiler med rsyslog, kan du derefter videresende logfilerne til et centraliseret logsystem, hvor de vil blive analyseret og klar til analyse i næsten realtid. Her er, hvordan en tidslinjegraf ser ud i Loggly. Den viser den samlede heap-størrelse samt heap-størrelsen før og efter garbage collector-kørslen.
Hukommelseslækager
Et hukommelseslækage er en situation, hvor et program allokerer hukommelse hurtigere, end det kan frigive den. Den nemmeste måde at opdage en hukommelseslækage på er, når dit program ikke reagerer, bliver ustabilt eller forårsager OutOfMemoryErrors
. I Java finder du flere tilfælde af Full GC
-samlinger, efterhånden som hukommelsesforbruget stiger.
Du kan få mere at vide om garbage collectorens output og lokalisering af hukommelseslækager i kapitlet Fejlfinding af hukommelseslækager i Java-dokumentationen.
Supplerende ressourcer
Kort debuggingtid uden at skrive en eneste linje kode (Loggly)-Guide til brug af Takipi
Den ultimative guide: 5 Methods for Debugging Production Servers at Scale (High Scalability)-Værktøjer og teknikker til fejlfinding af produktionsproblemer
Memory Management
Garbage Collection Tuning Guide (Oracle)-Guide til forståelse og finjustering af Javas garbage collection
Understanding Java Garbage Collection (CUBRID Blog)-Guide til garb
Se det. Analyser det. Inspicer det. Løs det
Se, hvad der betyder noget.
START GRATIS PRØVEPLEJE