Allt du behöver veta om minnesläckor i Android.
En av de centrala fördelarna med Java, eller för att vara mer exakt, av JVM (Java Virtual Machine), är skräpplockaren (Garbage Collector, GC). Vi kan skapa nya objekt utan att behöva oroa oss för att frigöra dem från minnet. Garbage Collector tar hand om att allokera och frigöra minnet åt oss.
Inte precis! Vi kan hindra garbage collector från att frigöra minnet åt oss om vi inte förstår helt och hållet hur GC fungerar.
Skriva en kod utan att förstå hur GC fungerar kan ge upphov till minnesläckor i appen. Dessa läckor kan påverka vår app genom att slösa bort outnyttjat minne och så småningom orsaka out of memory-exceptioner och förseningar.
- Vad är minnesläckage?
- Vänta lite!!🥴
- Heap & Stack
- Mer om Heap-minne
- Har du någonsin undrat vad heap-storleken för din applikation är?
- Hur kan du kontrollera appens heap-storlek för din enhet?
- Hur fungerar det i verkligheten?
- Vad händer när metoder avslutas?
- Slutsats
- Hur är det med heap?
- Hur fungerar garbage collector?
- Vad händer när skräpplockaren körs?
- När och hur uppstår minnesläckor?
- Hur vi kan orsaka en läcka?
- Hur kan vi orsaka en läcka med hjälp av trådar?
- Reguljärt flöde
Vad är minnesläckage?
Svikt på att frigöra oanvända objekt från minnet
Svikt på att frigöra oanvända objekt från minnet innebär att det finns oanvända objekt i applikationen som GC inte kan rensa från minnet.
När GC inte kan rensa de oanvända objekten från minnet är vi illa ute. Den minnesenhet som innehåller de oanvända objekten kommer att vara upptagen till slutet av programmet eller (till slutet av metoden).
Till slutet av metoden? Ja, det stämmer. Vi har två typer av läckor, läckor som upptar minnesenheten fram till slutet av programmet och läckor som upptar minnesenheten fram till slutet av metoden. Den första är tydlig. Den andra behöver förtydligas ytterligare. Låt oss ta ett exempel för att förklara detta! Anta att vi har metod X. Metod X utför någon långkörningsuppgift i bakgrunden, och det kommer att ta en minut att avsluta den. Dessutom håller metod X oanvända objekt medan den gör detta. I det fallet kommer minnesenheten att vara upptagen och de oanvända objekten kan inte rensas under en minut förrän uppgiften är avslutad. Efter metodens avslutande kan GC rensa de oanvända objekten och återta minnet.
Det är vad jag vill att ni ska veta för tillfället vi kommer att återkomma till detta senare med lite kod och visualisering. DET KOMMER ATT BLI ROLIGT. 👹😜
Vänta lite!!🥴
För att hoppa till sammanhanget börjar vi med grunderna.
Bilden nedan visar en representation av applikationens heap och stack och var varje objekt pekar och lagras när vi kör applikationen.
Vi kommer att gå igenom appens exekvering, stanna varje rad, förklara när applikationen allokerar objekten och lagrar dem i heap eller stack. Vi kommer också att se när appen släpper objekten från stacken och heap.
- Linje 1 – JVM:en skapar ett minnesblock i stacken för huvudmetoden.
- Linje 2 – I den här raden skapar vi en primitiv lokal variabel. Variabeln kommer att skapas och lagras i huvudmetodens stapelminne.
- Linje 3 -Här behöver jag er uppmärksamhet!!! I den här raden skapar vi ett nytt objekt. Objektet skapas i stacken i huvudmetoden och lagras i heap. Stacken lagrar referensen, minnesadressen (pointer) mellan objekt och heap, medan heap lagrar det ursprungliga objektet.
- Linje 4 – Samma sak som linje 3.
- Linje 5 – JVM skapar ett stackminnesblock för metoden foo.
- Rad 6 -Skapa ett nytt objekt. Objektet skapas i stackminnet för metoden foo, och vi lagrar i stacken heapminnesadressen för det objekt vi skapade i rad 3. Värdet (heapminnesadressen för objektet i rad 3) som vi överlämnade i rad 5. Kom ihåg att Java alltid överlämnar referenser genom värde.
- Linje 7 – Vi skapar ett nytt objekt. Objektet skapas i stacken och pekar på strängpoolen i heap.
- Linje 8 – I den sista raden i metoden foo avslutas metoden. Och objekten kommer att släppas från stackblocket i foo-metoden.
- Linje 9- Samma sak som i linje 8, I den sista linjen i main-metoden, avslutades metoden. Och stackblocket för huvudmetoden blir fritt.
Hur är det med att frigöra minnet från heap? Vi kommer att komma dit snart. Ta en kaffe☕️, och fortsätt 😼.
Vad händer när metoder avslutas?
Varje metod har sitt eget scope. När metoden avslutas släpps objekten och återtas automatiskt från stapeln.
I figur 1, när metoden foo
avslutas. Stackminnet eller stackblocket i foo-metoden frigörs och återkrävs automatiskt.
I figur 2, Samma sak. När metoden main
avslutades. Stackminnet eller stackblocket i huvudmetoden frigörs och återkrävs automatiskt.
Slutsats
Nu står det klart för oss att objekten i stapeln är tillfälliga. När metoden avslutas kommer objekten att släppas och återkrävas.
Stacken är en LIFO-datastruktur (Last-In-First-Out). Du kan se den som en låda. Genom att använda denna struktur kan programmet enkelt hantera alla sina operationer med hjälp av två enkla operationer: push och pop.
Varje gång du behöver spara något, t.ex. en variabel eller en metod, trycker och flyttar den stackpekaren uppåt. Varje gång du avslutar en metod, poppar den upp allt från stackpekaren tills du återvänder till adressen för den föregående metoden. I vårt exempel återvänder man från foo-metoden till huvudmetoden.
Hur är det med heap?
Heap skiljer sig från stacken. För att frigöra och återkräva objekten från heapminnet behöver vi hjälp.
För detta har Java, eller för att vara mer exakt, JVM gjort en superhjälte för att hjälpa oss. Vi kallar den Garbage Collector. Han kommer att göra det svåra arbetet åt oss. Och bry sig om att upptäcka oanvända objekt, släppa dem och återta mer utrymme i minnet.
Hur fungerar garbage collector?
Enkel. Garbage collector letar efter oanvända eller oåtkomliga objekt. När det finns ett objekt i heap som det inte pekas någon referens till tar garbage collector hand om att släppa det från minnet och återkräva mer utrymme.
GC-rötter är objekt som refereras av JVM. De är de första objekten i trädet. Varje objekt i trädet har ett eller flera rotobjekt. Så länge programmet eller GC-rötterna kan nå dessa rötter eller dessa objekt är hela trädet nåbart. När de blir ouppnåeliga från programmet eller GC-rötterna betraktas de som oanvända objekt eller ouppnåeliga objekt.
Vad händer när skräpplockaren körs?
För tillfället är detta programmets aktuella minnestillstånd. Stacken är rensad och högen är full av oanvända objekt.
Efter att ha kört GC blir resultatet följande:
GC kommer att frigöra och rensa alla oanvända objekt från heap.
Man! Hur blir det med minnesläckan som vi väntar på? LOL, bara en liten bit till så är vi framme. I vårt enkla program var programmet skrivet bra och enkelt. Det var inget fel på koden som kan hindra GC från att släppa heapens objekt. Och av den anledningen släpper GC och återkräver alla objekt från heapminnet. Fortsätt. Vi har många exempel på minnesläckor i nästa avsnitt😃.
När och hur uppstår minnesläckor?
En minnesläcka uppstår när stapeln fortfarande refererar till oanvända objekt i heap.
Det finns en enkel visuell representation i bilden nedan för en bättre förståelse av begreppet.
I den visuella representationen ser vi att när vi har objekt som refereras från stapeln men som inte längre används. Garbage Collector kommer aldrig att släppa eller frigöra dem från minnet eftersom det visar att dessa objekt används medan de inte gör det.
Hur vi kan orsaka en läcka?
Det finns olika sätt att orsaka en minnesläcka i Android. Och det kan göras enkelt med hjälp av AsyncTasks, Handlers, Singleton, Threads med mera.
Jag kommer att visa några exempel med hjälp av trådar, singleton och lyssnare för att förklara hur vi kan orsaka en läcka och undvika och åtgärda dem.
Kolla in mitt Github repository. Jag har några kodexempel.
Hur kan vi orsaka en läcka med hjälp av trådar?
I det här exemplet ska vi starta en aktivitet som kör en tråd i bakgrunden. Tråden kommer att utföra en uppgift som tar 20 sekunder att slutföra.
Som bekant har de inre klasserna en implicit referens till den omslutande klassen.
Här bakom kulisserna är det faktiskt så här aktiviteten ser ut.
The DownloadTask har en referens till ThreadActivity.
Så vad händer efter att ha startat uppgiften eller tråden?
Det finns två möjliga flöden för att använda programmet. Ett vanligt flöde som fungerar som förväntat utan fel och ett läckageflöde som orsakar en minnesläcka.
Reguljärt flöde
I bilden presenterar vi applikationens heap och stack.
Användaren startar programmet, öppnar ThreadActivity och väntar på skärmen tills nedladdningsuppgiften är klar. Användaren väntade i 20 sekunder. Varför 20 sekunder? Därför att det är den tid det tar för tråden att slutföra uppgiften.
Uppgiften körs i bakgrunden. Användaren väntade i 20 sekunder på att nedladdningsuppgiften skulle slutföras. När uppgiften är klar släpper stacken metodblocket run().
Det finns ingen referens som håller DownloadTask. GC betraktade DownladTask-objektet som ett oanvänt objekt och av den anledningen kommer nästa GC-cykel att rensa det från heapminnet.
GC rensar de oanvända objekten från heapminnet. När användaren nu stänger aktiviteten. Huvudmetoden kommer att släppas från stacken, och i nästa GC-cykel kommer GC att rensa ThreadActivity från heapminnet.
Perfekt!