Minnelekkasje – Hva? Hvor? Hvorfor?

Minnelekkasje er et fenomen som kanskje mange har hørt om, men ikke nødvendigvis vet helt hva er. De oppdages som oftest som følge av at en applikasjon etter en viss tid blir treg, henger – eller i verste fall tar ned hele noden den kjører på. De er notorisk vanskelige å debugge, og kan være tidkrevende å løse.

For alle graf-tilhengere, vil følgende paint-graf gi mye glede, samt enkelt illustrere hvordan minnelekkasjen vil fortone seg.

minnelekkasje

Avansert graf for å demonstrere hvordan minnelekkasje fortoner seg over tid, hvis du overvåker minneforbruk.

Denne typen bugs kan være svært vanskelige å finne ut av, da man ofte ikke får noen gode indikasjoner fra applikasjonen hvor det lekker minne. Dette gjør at feilsøking er tidkrevende og komplekst. Man skjønner da at jo lenger man er kommet i utviklingsløpet, jo dyrere blir dette. Jeg har vært borte i applikasjoner hvor man er klar over at den har en minnelekkasje «ett eller annet sted», men ikke har klart å identifisere hvor. Den midlertidig løsningen er å restarte applikasjonen med jevne mellomrom i produksjon. Dette er ikke en holdbar situasjon!

Dette er ikke aktuelt for meg! Eller?

Så hvordan unngå slike situasjoner? Stikkord her er å oppdage feilene tidlig, samt unngå å falle i et par vanlige feller når man utvikler nye applikasjoner. Jeg skal gi et par eksempler på typiske programmeringsfeil som kan skape minnelekkasjer i applikasjoner.

  • Objekter som aldri blir garbage collected (GC), som følge av at de aldri blir sluppet. Her demonstrert ved en statisk variabel som holder objektreferanse:

class MinneLekkasjeClass {
   static final ArrayList list = new ArrayList(1000);
}

Dette er kanskje den vanligste typen programmeringsfeil/designfeil som skaper minnelekkasjer. Husk å fjerne referanser til objekter som ikke lenger skal være tilgjengelige!

  • Glemme å lukke streams/connections:
 try {
   BufferedReader br = new BufferedReader(new FileReader(inputFile));
   Connection kobling = ConnectionFactory.getConnection(); 
   ..... 
   masseSpennendeMetoder(); 
   ..... 
   glemmeClose();
} catch (Exception e) { 
   e.kastException(); 
} 

Dette er ikke nødvendigvis en minnelekkasje, da disse hengende koblingene kan bli plukket opp av GC. Nok en gang: Kan. Så god praksis å alltid lukke disse eksplisitt.

 

  • Forkbomb; gøy på skolen – ikke veldig gøy i applikasjoner. For uinnvidde, så starter dette med en tråd eller prosess som forkes (lager en kopi av seg selv). De to identiske trådene/prosessene forker deretter på nytt, og du har det gående helt til du går tom for minne
  • Cacher kan også skape hodebry ved å holde på referanser til klasser/objekter som blir forsøkt å kjøre garbage collection på. Dette er et eget kapittel for seg selv, og nøyer meg med å kun nevne det her.

Dette er selvfølgelig bare noen av situasjonene som kan forårsake minnelekkasje, men er viktig å merke seg da disse kan oppstå i programmeringsspråk som håndterer minneallokering og garbage collection selv. Det er lett for utviklere å ikke tenke over minneforbruk, og hvile på oppfatningen av at minnehåndtering er noe man kun trenger tenke på hvis man koder i C.

Beste scenario er altså om man unngår å introdusere minnelekkasjer ved utvikling. Dette kan man oppnå ved å innarbeide seg noen enkle rutiner, deriblant å tenke over situasjoner som de som er nevnt over. Dette sparer masse tid og penger ved eventuell feilsøking senere i utviklingsløpet!

Har du erfaringer eller spørmål knyttet til temaet? – Ikke vær redd for å stille de i kommentarfeltet under, eller send meg en epost på: geir.lien@visma.com

Geir Josten Lien jobber som teknisk tester og testleder i Visma Consulting. Geir er fagmanager for teknisk test, og innovasjonsleder for teamet hos Skatteetaten. Han har jobbet i Visma siden 2012, og har bakgrunn fra High-Performance Computing ved NTNU.
Kontakt Geir: