Förstå Garbage Collection i AS3

Har du någonsin använt en Flash-applikation och märkt lagret i den? Fortfarande vet inte varför det coola flashspelet går sakta på din dator? Om du vill veta mer om en möjlig orsak till det, är den här artikeln för dig.

Vi hittade denna fantastiska författare tack vare FlashGameLicense.com, platsen att köpa och sälja Flash-spel!

Publicerad handledning

Varje par veckor besöker vi några av våra läsares favoritinlägg från hela webbplatsens historia. Denna handledning publicerades först i juni 2010.


Slutresultatförhandsvisning

Låt oss ta en titt på det slutliga resultatet vi ska arbeta för:


Steg 1: En snabb körning genom referens

Innan vi kommer in i det verkliga ämnet behöver du först veta lite om hur instantiating och referencing fungerar i AS3. Om du redan har läst om det rekommenderar jag fortfarande att läsa detta lilla steg. På så sätt kommer all kunskap att vara frisk i ditt huvud och du kommer inte ha problem med att läsa resten av denna snabba tips!

Skapandet och referensen av instanser i AS3 är annorlunda än de flesta tror. Inställningen (eller "skapandet") av något händer bara när koden frågar om att skapa ett objekt. Vanligtvis händer detta genom det "nya" sökordet, men det är också närvarande när du använder en bokstavlig syntax eller definiera parametrar för funktioner, till exempel. Exempel på detta visas nedan:

 // Instantiation genom det "nya" sökordet nytt objekt (); ny Array (); ny int (); ny sträng (); nya booleska (); ny datum (); // Instantiation genom bokstavlig syntax ; []; 5 "Hej värld!" true // Instantiation via funktionsparametrar privat funktion tutExample (parameter1: int, parameter2: Boolean): tomrum

När ett objekt har skapats kommer det att förbli ensam tills något hänvisar till det. För att göra det skapar du vanligtvis en variabel och skickar objektets värde till variabeln så att den vet vilket objekt det för närvarande håller. Men (och det här är den del som de flesta inte vet), när du överför en variabels värde till en annan variabel skapar du inte ett nytt objekt. Du skapar istället en annan länk till objektet som båda variablerna nu håller! Se bilden nedan för att förtydliga:

Bilden förutsätter båda Variabel 1 och Variabel 2 kan hålla smiley (dvs de kan hålla samma typ). På vänster sida, bara Variabel 1 existerar. Men när vi skapar och ställer in Variabel 2 till samma värde av Variabel 1, Vi skapar inte en länk mellan Variabel 1 och Variabel 2 (högra höger del av bilden), istället skapar vi en länk mellan Smiley och Variabel 2 (nedre högra delen av bilden).

Med denna kunskap kan vi hoppa till Garbage Collector.


Steg 2: Varje stad behöver en sopor

Det är uppenbart att varje applikation behöver en viss mängd minne att köra, eftersom det behöver variabler att hålla värden och använda dem. Det som inte är klart är hur programmet hanterar objekt som inte längre behövs. Återvinns det? Tar det bort dem? Lämna objektet i minnet tills applikationen är stängd? Alla tre alternativen kan hända, men här kommer vi att tala specifikt om andra och tredje.

Föreställ dig en situation där en applikation skapar många objekt när den initialiseras, men när denna period slutar mer än hälften av de skapade objekten fortfarande är oanvända. Vad skulle hända om de lämnades i minnet? De skulle säkert ta mycket utrymme i det, vilket orsakar det som folk kallar eftersläpning, vilket är en märkbar nedgång i ansökan. De flesta användare tycker inte om det här, så vi måste undvika det. Hur kan vi koda för att effektivisera programmet? Svaret finns i Skräp samlare.

Garbage Collector är en form av minneshantering. Det syftar till att eliminera objekt som inte används och upptar utrymme i systemets minne. På så sätt kan applikationen köras med minimal minnesanvändning. Låt oss se hur det fungerar:

När din ansökan börjar köra, frågar den om ett minne från systemet som kommer att användas av programmet. Applikationen börjar sedan fylla i det här minnet med all information du behöver, varje objekt du skapar går in i det. Men om minnesanvändningen kommer nära det initierade minnet, körs sopkollektorn, och söker efter något objekt som inte används för att tömma lite utrymme i minnet. Ibland orsakar det lite fördröjning i applikationen, på grund av det stora överskottet av objektsökning.

I bilden kan du se minnetoppar (cirklade i grönt). Topparna och den plötsliga droppen orsakas av sopsamlaren, som fungerar när applikationen har nått den begärda minnesanvändningen (den röda linjen), tar bort alla onödiga objekt.


Steg 3: Starta SWF-filen

Nu när vi vet vad Garbage Collector kan göra för oss, är det dags att lära sig hur man kodar för att få alla fördelar med det. Först och främst behöver vi veta hur Garbage Collector fungerar, i en praktisk bild. I koden blir objekten berättigade till sopkollektion när de blir oåtkomliga. När ett objekt inte kan nås förstår koden att den inte kommer att användas längre, så det måste samlas in.

ActionScript 3 kontrollerar tillgänglighet genom sopor insamling rötter. För tillfället kan ett objekt inte nås genom en soprotsrot, det blir berättigat till insamling. Nedan ser du en lista över de viktigaste skräpsamlingsroten:

  • Paketnivå och statiska variabler.
  • Lokala variabler och variabler inom ramen för en exekveringsmetod eller -funktion.
  • Instansvariabler från programmets huvudklassinstans eller från visningslistan.

För att förstå hur objekt hanteras av Sopkollektor måste vi koda och undersöka vad som händer i exempelfilen. Jag kommer att använda FlashDevelops AS3-projekt och Flexs kompilator, men jag antar att du kan göra det på vilken IDE du vill, eftersom vi inte kommer att använda specifika saker som bara finns i FlashDevelop. Jag har byggt en enkel fil med en knapp och textstruktur. Eftersom detta inte är målet i detta snabba tips, kommer jag snabbt att förklara det: när en knapp klickar, brinner en funktion. Vi vill när som helst visa någon text på skärmen, du ringer en funktion med texten och den visas. Det finns också ett annat textfält för att visa en beskrivning för knappar.

Syftet med vår exempelfil är att skapa objekt, radera dem och undersöka vad som händer med dem efter att de har raderats. Vi behöver ett sätt att veta om objektet är levande eller inte, så vi lägger till en ENTER_FRAME-lyssnare för varje objekt och får dem att visa lite text med tiden de har levt i. Så låt oss koda det första objektet!

Jag skapade en rolig smileybild för objekten, till hyllning till Michael James Williams stora Avoider-spelhandledning, som också använder smileybilder. Varje objekt kommer att ha ett nummer på huvudet, så vi kan identifiera det. Jag namngav också det första objektet TheObject1, och det andra objektet TheObject2, så det blir lätt att skilja. Låt oss gå till koden:

 privat var _theObject1: TheObject1; privat funktion newObjectSimple1 (e: MouseEvent): void // Om det redan finns ett objekt skapat, gör inget om (_theObject1) returnera; // Skapa det nya objektet, sätt det till läget det ska vara i och lägg till i visningslistan så att vi kan se att den skapades _theObject1 = new TheObject1 (); _theObject1.x = 320; _theObject1.y = 280; _theObject1.addEventListener (Event.ENTER_FRAME, changeTextField1); addChild (_theObject1); 

Det andra objektet ser nästan ut. Här är det:

 privat var _theObject2: TheObject2; privat funktion newObjectSimple2 (e: MouseEvent): void // Om det redan finns ett objekt skapat, gör inget om (_theObject2) returnera; // Skapa det nya objektet, sätt det till läget det ska vara i och lägg till i visningslistan så att vi kan se att den skapades _theObject2 = new TheObject2 (); _theObject2.x = 400; _theObject2.y = 280; _theObject2.addEventListener (Event.ENTER_FRAME, changeTextField2); addChild (_theObject2); 

I koden, newObjectSimple1 () och newObjectSimple2 () är funktioner som avfyras när motsvarande knapp klickas. Dessa funktioner skapar helt enkelt ett objekt och lägger till det på skärmen, så vi vet att det skapades. Dessutom skapar det en ENTER_FRAME händelse lyssnare i varje objekt, vilket får dem att visa ett meddelande varje sekund så länge de är aktiva. Här är funktionerna:

 privat funktion changeTextField1 (e: Event): void // Vårt exempel körs vid 30FPS, så låt oss lägga till 1/30 på varje ram i räkningen. _objectCount1 + = 0,034; // Kontrollerar om _objectCount1 har passerat en sekund om (int (_objectCount1)> _secondCount1) // Visar en text i skärmdisplayText ("Objekt 1 är levande ..." + int (_objectCount1)); _secondCount1 = int (_objectCount1); 
 privat funktion changeTextField2 (e: Event): void // Vårt exempel körs vid 30FPS, så låt oss lägga till 1/30 på varje ram i räkningen. _objectCount2 + = 0,034; // Kontrollerar om _objectCount2 har passerat en sekund om (int (_objectCount2)> _secondCount2) // Visar en text i skärmdisplayText ("Objekt 2 är levande ..." + int (_objectCount2)); _secondCount2 = int (_objectCount2); 

Dessa funktioner visar helt enkelt ett meddelande på skärmen med tiden objekten har levt. Här är SWF-filen med det nuvarande exemplet:


Steg 4: Radera objekten

Nu när vi har täckt skapandet av objekt, låt oss försöka något: har du någonsin undrat vad som skulle hända om du faktiskt raderar (ta bort alla referenser) ett objekt? Skaffas det skräp? Det är vad vi ska testa nu. Vi ska bygga två raderknappar, en för varje objekt. Låt oss göra koden för dem:

 privat funktion deleteObject1 (e: MouseEvent): void // Kontrollera om _theObject1 verkligen existerar innan du tar bort det från visningslistan om (_theObject1 && contains (_theObject1)) removeChild (_theObject1); // Ta bort alla referenser till objektet (det här är den enda referensen) _theObject1 = null; // Visar en text i skärmdisplayText ("Deleted object 1 successfully!"); 
 privat funktion deleteObject2 (e: MouseEvent): void // Kontrollera om _theObject2 verkligen existerar innan du tar bort den från visningslistan om (_theObject1 && contains (_theObject2)) removeChild (_theObject2); // Ta bort alla referenser till objektet (detta är den enda referensen) _theObject2 = null; // Visar en text i skärmdisplayText ("Deleted object 2 successfully!"); 

Låt oss ta en titt på SWF nu. Vad tror du kommer hända?

Som du kan se. Om du klickar på "Skapa objekt1" och sedan "Radera objekt1", händer ingenting! Vi kan berätta koden körs, eftersom texten visas på skärmen, men varför blir objektet inte raderat? Objektet finns fortfarande där eftersom det inte faktiskt togs bort. När vi rensade alla referenser till det, berättade vi koden för att göra den berättigad till skräpuppsamling, men skräpuppsamlaren kör aldrig. Kom ihåg att skräpuppsamlaren endast körs när den aktuella minnesanvändningen kommer nära det begärda minnet när programmet startade. Det är meningslöst, men hur ska vi testa detta??

Jag kommer verkligen inte att skriva en bit kod för att fylla vår ansökan med värdelösa objekt tills minnesanvändningen blir för stor. Istället använder vi en funktion som för närvarande inte stöds av Adobe, enligt Grant Skinner-artikeln, som tvingar sophämtaren att springa. På så sätt kan vi utlösa denna enkla metod och se vad som händer när det körs. Från och med nu kommer jag att hänvisa till Garbage Collector som GC, för enkelhetens skull. Här är funktionen:

 privat funktionskraftGC (e: MouseEvent): void försök new LocalConnection (). connect ('foo'); nya LocalConnection (). connect ('foo');  fångst (e: *)  // Visar en text i skärmdisplayText ("----- Garbage collection triggered -----"); 

Den här enkla funktionen, som endast skapar två LocalConnection () -objekt, är känd för att tvinga GC att köra, så vi kommer att ringa det när vi vill att detta ska hända. Jag rekommenderar inte att du använder den här funktionen i en seriös applikation. Om du gör det för test, finns det inga riktiga problem, men om det gäller en applikation som kommer att distribueras till människor är det inte en bra funktion att använda eftersom det kan medföra negativa effekter.

Vad jag rekommenderar för fall som detta är att du bara låter GC springa i sin egen takt. Försök inte tvinga det. Istället fokuserar du på kodning effektivt så att minnesproblem inte händer (vi kommer att täcka detta i steg 6). Låt oss nu titta på vårt exempel SWF igen och klicka på knappen "Collect Garbage" efter att ha skapat och raderat ett objekt.

Har du testat filen? Det fungerade! Du kan se att nu, när du har raderat ett objekt och utlöst GC, tar det bort objektet! Observera att om du inte tar bort objektet och ringer till GC kommer inget att hända, eftersom det fortfarande finns en referens till det objektet i koden. Nu, vad händer om vi försöker hålla två referenser till ett objekt och ta bort en av dem?


Steg 5: Skapa en annan referens

Nu när vi har bevisat att GC fungerar precis som vi ville, låt oss försöka något annat: länka en annan referens till ett objekt (Object1) och ta bort originalet. Först måste vi skapa en funktion för att länka och koppla bort en referens till vårt objekt. Vi gör det:

 privat funktion saveObject1 (e: MouseEvent): void // _onSave är en booleska om du vill länka eller koppla bort referensen om (_onSave) // Om det inte finns något objekt att spara, gör ingenting om (! _theObject1)  // Visar en text i skärmdisplayText ("Det finns inget objekt 1 att spara!"); lämna tillbaka;  // En ny variabel för att hålla en annan referens till Object1 _theSavedObject = _theObject1; // Visar en text i skärmdisplayText ("Sparade objekt 1 lyckades!"); // Nästa gång den här funktionen körs, kopplar du bort den, eftersom vi bara kopplade _onSave = false;  else // Ta bort referensen till den _theSavedObject = null; // Visar en text i skärmdisplayText ("Ej sparat objekt 1 lyckades!"); // Nästa gång den här funktionen körs, länk den, eftersom vi bara kopplade bort _onSave = true; 

Om vi ​​testar vår swf nu kommer vi att märka att om vi skapar Object1, spara sedan, ta bort det och tvinga GC att springa, kommer inget att hända. Det beror på att nu, även om vi tog bort den "ursprungliga" länken till objektet, finns det fortfarande en annan referens till den, vilket gör att den inte kan komma ifrån sig. Detta är i princip allt du behöver veta om Garbage Collector. Det är ju inte ett mysterium. men hur ska vi tillämpa detta på vår nuvarande miljö? Hur kan vi använda denna kunskap för att förhindra att vår applikation löper långsamt? Så här visar steg 6 oss: hur man applicerar detta i reella exempel.


Steg 6: Gör din kod effektiv

Nu för det mesta: gör din kod effektiv med GC! Detta steg kommer att ge användbar information som du borde behålla under hela ditt liv - spara det ordentligt! Först vill jag presentera ett nytt sätt att bygga dina objekt i din ansökan. Det är ett enkelt men effektivt sätt att samarbeta med GC. På så sätt introduceras två enkla klasser, som kan utökas till andra, när du förstår vad det gör.

Tanken med det här sättet är att genomföra en funktion - kallad förstör () - på varje objekt som du skapar, och kalla det varje gång du slutar arbeta med ett objekt. Funktionen innehåller all kod som är nödvändig för att ta bort alla referenser till och från objektet (exklusive referensen som användes för att ringa funktionen), så se till att objektet lämnar din ansökan helt isolerad och är lätt igenkänd av GC. Anledningen till detta förklaras i nästa steg. Låt oss titta på den allmänna koden för funktionen:

 // Skapa det här i varje objekt som du använder offentlig funktion förstör (): void // Ta bort händelselyssnare // Ta bort allt i visningslistan // Rensa referenser till andra objekt, så det blir helt isolerat // ... // När du vill ta bort objektet gör du det här: theObject.destroy (); // Och sedan null den sista hänvisningen till den theObject = null;

I den här funktionen måste du rensa allt från objektet, så det är fortfarande isolerat i applikationen. Efter det har det varit lättare för GC att lokalisera och ta bort objektet. Låt oss nu titta på några av de situationer där de flesta minnesfel händer:

  • Objekt som endast används i ett intervall av körning: var försiktig med dessa, eftersom de kan vara de som konsumerar mycket minne. Dessa objekt existerar endast under en viss tid (till exempel för att lagra värden när en funktion körs) och de öppnas inte så ofta. Kom ihåg att ta bort alla referenser till dem när du är färdig med dem, annars kan du få många av dem i din ansökan, bara ta minnesutrymme. Tänk på att om du skapar många referenser till dem måste du eliminera var och en genom förstöra() fungera.
  • Objekt kvar i visningslistan: Ta alltid bort ett objekt från visningslistan om du vill radera det. Displaylistan är en av de sopor insamling rötter (kom ihåg det?) och så är det väldigt viktigt att du håller dina föremål borta från det när du tar bort dem.
  • Steg, förälder och rotreferenser: Om du gillar att använda mycket av dessa egenskaper, kom ihåg att ta bort dem när du är klar. Om många av dina föremål hänvisar till dessa kan du vara i trubbel!
  • Evenemangslyttare: Ibland är referensen som håller dina föremål från att samlas in en händelselysare. Kom ihåg att ta bort dem, eller använd dem som svaga lyssnare, om det behövs.
  • Arrays och vektorer: ibland kan dina arrays och vektorer ha andra objekt och lämna referenser inom dem som du kanske inte är medvetna om. Var försiktig med arrays och vektorer!

Steg 7: Referensön

Trots att det är bra att arbeta med GC är det inte perfekt. Du måste vara uppmärksam på vad du gör, annars kan dåliga saker hända med din ansökan. Jag skulle vilja visa ett problem som kan uppstå om du inte följer alla nödvändiga steg för att få din kod att fungera med GC korrekt.

Ibland kan du, om du inte rensar alla referenser till och från ett objekt, ha det här problemet, speciellt om du länkar många objekt ihop i din ansökan. Ibland kan en enda referens vänster räcka för att detta ska hända: Alla dina objekt utgör en ö referenser, där alla objekt är anslutna till andra, vilket inte tillåter GC att ta bort dem.

När GC körs, utför det två enkla uppgifter för att kontrollera objekt som ska raderas. En av dessa uppgifter räknar hur många referenser varje objekt har. Alla objekt med 0 referenser samlas in samtidigt. Den andra uppgiften är att kontrollera om det finns något litet antal objekt som länkar till varandra, men det går inte att komma åt, vilket sparar minne. Kolla bilden:

Som du kan se kan de gröna objekten inte nås, men deras referensräkning är 1. GC utför den andra uppgiften för att söka efter denna bit av objekt och avlägsnar dem alla. Men när chunken är för stor, ger GC "upp" vid kontroll och förutsätter att föremålen kan nås. Tänk nu om du har något sådant:

Detta är ön referenser. Det skulle ta mycket minne från systemet, och skulle inte samlas in av GC på grund av dess komplexitet. Det låter ganska dåligt, va? Det kan emellertid lätt undvikas, dock. Se bara till att du har rensat varje referens till och från ett objekt, och då kommer skrämmande saker som inte att hända!


Slutsats

Detta är det för nu. I denna Quick Tip lärde vi oss att vi kan göra vår kod bättre och effektivare för att minska lag- och minnesproblem, vilket gör det mer stabilt. För att kunna göra detta måste vi förstå hur referensobjekt fungerar i AS3, och hur man kan dra nytta av dem för att GC ska fungera korrekt i vår ansökan. Trots det faktum att vi kan göra vår ansökan bättre måste vi vara försiktiga när vi gör det - annars kan det bli jämnare och långsammare!

Jag hoppas att du gillade det här enkla tipset. Om du har några frågor, släpp en kommentar nedan!