Forstå Garbage Collection i AS3

Har du noen gang brukt en Flash-applikasjon og lagt merke til lag i det? Fortsatt ikke vet hvorfor det kule flash spillet kjører sakte på datamaskinen din? Hvis du vil vite mer om en mulig årsak til det, er denne artikkelen for deg.

Vi fant denne fantastiske forfatteren takket være FlashGameLicense.com, stedet å kjøpe og selge Flash-spill!

Publisert opplæring

Noen få uker besøker vi noen av leserens favorittinnlegg fra hele historien til nettstedet. Denne opplæringen ble først publisert i juni 2010.


Endelig resultatforhåndsvisning

La oss se på det endelige resultatet vi vil jobbe for:


Trinn 1: En rask gjennomgang gjennom referanse

Før vi kommer inn i det virkelige emnet, må du først vite litt om hvordan instantiating og referencing fungerer i AS3. Hvis du allerede har lest om det, anbefaler jeg fortsatt å lese dette lille trinnet. På den måten vil all kunnskap være frisk i hodet ditt, og du vil ikke ha problemer med å lese resten av denne raske tipsen!

Opprettelsen og referansen av forekomster i AS3 er annerledes enn de fleste tror. Instantiseringen (eller "opprettelsen") av noe skjer bare når koden ber om å skape et objekt. Vanligvis skjer dette via det "nye" søkeordet, men det er også tilstede når du bruker en bokstavelig syntaks eller definere parametere for funksjoner, for eksempel. Eksempler på dette er vist nedenfor:

 // Instantiation gjennom det "nye" søkeordet nytt objekt (); Ny Array (); ny int (); ny streng (); ny boolsk (); ny dato (); // Instantiation gjennom bokstavelig syntaks ; []; 5 "Hei verden!" true // Instantiation gjennom funksjonsparametere privat funksjon tutExample (parameter1: int, parameter2: boolsk): tomrom

Når et objekt er opprettet, vil det forbli alene til noe refererer til det. For å gjøre det, lager du vanligvis en variabel og sender objektets verdi til variabelen, slik at den vet hvilken gjenstand den for tiden har. Men (og dette er den delen de fleste ikke vet), når du overfører en variables verdi til en annen variabel, oppretter du ikke et nytt objekt. Du lager i stedet en annen lenke til objektet som begge variablene holder nå! Se bildet nedenfor for avklaring:

Bildet antar begge Variabel 1 og Variabel 2 kan holde smiley (dvs. de kan ha samme type). På venstre side, bare Variabel 1 eksisterer. Men når vi lager og setter Variabel 2 til samme verdi av Variabel 1, Vi oppretter ikke en kobling mellom Variabel 1 og Variabel 2 (øverste høyre del av bildet), i stedet lager vi en kobling mellom Smiley og Variabel 2 (nederste høyre del av bildet).

Med denne kunnskapen kan vi hoppe til Garbage Collector.


Trinn 2: Hver by trenger en søppelkollektor

Det er åpenbart at hver applikasjon trenger en viss mengde minne å kjøre, fordi det trenger variabler for å holde verdier og bruke dem. Det som ikke er klart, er hvordan programmet håndterer gjenstandene som ikke er nødvendig lenger. Gjenvinner de dem? Sletter det dem? Går det gjenstanden i minnet til søknaden er lukket? Alle tre alternativene kan skje, men her snakker vi spesielt om andre og tredje.

Tenk deg en situasjon der et program skaper mange objekter når den initialiseres, men når denne perioden slutter, er mer enn halvparten av gjenstandene som er opprettet, ubrukt. Hva ville skje hvis de ble igjen i minnet? De ville sikkert ta mye plass i det, og dermed forårsake det folk roper lag, som er en merkbar sakte ned i søknaden. De fleste brukere vil ikke like dette, så vi må unngå det. Hvordan kan vi kode for å få programmet til å løpe mer effektivt? Svaret er i Søppelmann.

Garbage Collector er en form for minnehåndtering. Det tar sikte på å eliminere ethvert objekt som ikke er brukt og opptar plass i systemets minne. På denne måten kan programmet kjøres med minimal minnebruk. La oss se hvordan det fungerer:

Når søknaden din begynner å løpe, ber den om en mengde minne fra systemet som vil bli brukt av programmet. Programmet starter da fylling av dette minnet med all informasjon du trenger; hvert objekt du lager, går inn i det. Men hvis minnebruken kommer nær det minnet som er forespurt om, begynner Garbage Collector, og søker et objekt som ikke brukes til å tømme noen plass i minnet. Noen ganger fører dette til litt forsinkelse i applikasjonen, på grunn av den store overhead for objektsøking.

På bildet kan du se minne toppene (sirklet i grønt). Toppene og det plutselige fallet er forårsaket av søppelsamleren, som virker når applikasjonen har nådd den forespurte minnesbruken (den røde linjen), fjerner alle unødvendige gjenstander.


Trinn 3: Starte SWF-filen

Nå som vi vet hva Garbage Collector kan gjøre for oss, er det på tide å lære å kode for å få alle fordelene av det. Først og fremst må vi vite hvordan Garbage Collector fungerer, i en praktisk visning. I koden blir objekter kvalifisert for søppelsamling når de blir uoppnåelige. Når et objekt ikke kan nås, forstår koden at det ikke blir brukt lenger, så det må samles inn.

Actionscript 3 sjekker tilgjengeligheten gjennom søppel innsamling røtter. For øyeblikket er det ikke mulig å få tilgang til en gjenstand gjennom en søppelsamlingsrotte, men blir berettiget til innsamling. Nedenfor ser du en liste over de viktigste søppelsamlingsrøttene:

  • Pakkenivå og statiske variabler.
  • Lokale variabler og variabler i omfanget av en eksekveringsmetode eller -funksjon.
  • Instansvariabler fra programmets hovedklasseksempel eller fra visningslisten.

For å forstå hvordan objekter håndteres av Garbage Collector, må vi kode og undersøke hva som skjer i eksempelfilen. Jeg skal bruke FlashDevelops AS3-prosjekt og Flexs kompilator, men jeg antar at du kan gjøre det på en IDE du vil, siden vi ikke skal bruke bestemte ting som bare finnes i FlashDevelop. Jeg har bygget en enkel fil med en knapp og tekststruktur. Siden dette ikke er målet i dette raske tipset, vil jeg raskt forklare det: Når en knapp klikkes, brenner en funksjon. Når som helst vi ønsker å vise litt tekst på skjermen, ringer du en funksjon med teksten, og den vises. Det finnes også et annet tekstfelt for å vise en beskrivelse for knapper.

Målet med vår eksempelfil er å lage objekter, slette dem og undersøke hva som skjer med dem etter at de er slettet. Vi trenger en måte å vite om objektet er i live eller ikke, så vi vil legge til en ENTER_FRAME-lytter til hver av objektene, og få dem til å vise litt tekst med tiden de har levd i. Så la oss kode det første objektet!

Jeg opprettet et morsomt smileybilde for objektene, i hyllest til Michael James Williams fantastiske Avoider-spillopplæringen, som også bruker smileybilder. Hvert objekt vil ha et nummer på hodet, slik at vi kan identifisere det. Jeg har også kalt det første objektet TheObject1, og det andre objektet TheObject2, så det vil være lett å skille mellom. La oss gå til koden:

 privat var _theObject1: TheObject1; privat funksjon newObjectSimple1 (e: MouseEvent): void // Hvis det allerede er opprettet et objekt, gjør ingenting hvis (_theObject1) returnerer; // Opprett det nye objektet, sett det til posisjonen den skal være i, og legg til i visningslisten slik at vi kan se at den ble opprettet _theObject1 = new TheObject1 (); _theObject1.x = 320; _theObject1.y = 280; _theObject1.addEventListener (Event.ENTER_FRAME, changeTextField1); addChild (_theObject1); 

Det andre objektet ser nesten det samme ut. Her er det:

 privat var _theObject2: TheObject2; privat funksjon newObjectSimple2 (e: MouseEvent): void // Hvis det allerede er opprettet et objekt, gjør ingenting hvis (_theObject2) returnerer; // Opprett det nye objektet, sett det til posisjonen den skal være i, og legg til i visningslisten slik at vi kan se at den ble opprettet _theObject2 = new TheObject2 (); _theObject2.x = 400; _theObject2.y = 280; _theObject2.addEventListener (Event.ENTER_FRAME, changeTextField2); addChild (_theObject2); 

I koden, newObjectSimple1 () og newObjectSimple2 () er funksjoner som avfyres når den tilhørende knappen klikkes. Disse funksjonene oppretter bare et objekt og legger det til på skjermen, så vi vet at det ble opprettet. I tillegg skaper det en ENTER_FRAME hendelse lytter i hvert objekt, som vil få dem til å vise en melding hvert sekund, så lenge de er aktive. Her er funksjonene:

 privat funksjon changeTextField1 (e: Event): void // Vårt eksempel kjører på 30FPS, så la oss legge til 1/30 på hver ramme i tellingen. _objectCount1 + = 0,034; // Sjekk for å se om _objectCount1 har passert et sekund, hvis (int (_objectCount1)> _secondCount1) // Viser en tekst i skjermbildetText ("Objekt 1 er i live ..." + int (_objectCount1)); _secondCount1 = int (_objectCount1); 
 privat funksjon changeTextField2 (e: Event): void // Vårt eksempel kjører på 30FPS, så la oss legge til 1/30 på hver ramme i tellingen. _objectCount2 + = 0,034; // Sjekk for å se om _objectCount2 har passert et sekund, hvis (int (_objectCount2)> _secondCount2) // Viser en tekst på skjermbildetText ("Objekt 2 er i live ..." + int (_objectCount2)); _secondCount2 = int (_objectCount2); 

Disse funksjonene viser bare en melding på skjermen med tiden objekter har levd. Her er SWF-filen med nåværende eksempel:


Trinn 4: Slette objektene

Nå som vi har dekket opprettelsen av objekter, la oss prøve noe: Har du noen gang lurt på hva som ville skje hvis du faktisk sletter (fjern alle referanser) et objekt? Går det søppel innsamlet? Det er det vi skal teste nå. Vi skal bygge to slette knapper, en for hvert objekt. La oss lage koden for dem:

 privat funksjon deleteObject1 (e: MouseEvent): void // Sjekk om _theObject1 egentlig eksisterer før du fjerner det fra visningslisten hvis (_theObject1 && contains (_theObject1)) removeChild (_theObject1); // Fjerne alle referanser til objektet (dette er den eneste referansen) _theObject1 = null; // Viser en tekst i skjermbildet Tekst ("Slettet objekt 1 er vellykket!"); 
 privat funksjon deleteObject2 (e: MouseEvent): void // Sjekk om _theObject2 virkelig eksisterer før du fjerner det fra visningslisten hvis (_theObject1 && contains (_theObject2)) removeChild (_theObject2); // Fjerne alle referanser til objektet (dette er den eneste referansen) _theObject2 = null; // Viser en tekst i skjermbildet Tekst ("Slettet objekt 2 vellykket!"); 

La oss ta en titt på SWF nå. Hva tror du vil skje?

Som du kan se. Hvis du klikker på "Opprett Object1" og deretter "Slett Object1", skjer ingenting virkelig! Vi kan fortelle kodekjøringene, fordi teksten vises på skjermen, men hvorfor blir ikke objektet slettet? Objektet er fremdeles der fordi det ikke ble fjernet. Når vi fjernet alle referanser til det, fortalte vi koden for å gjøre det berettiget til søppelsamling, men søppelsamleren løper aldri. Husk at søppelkollektoren bare vil kjøre når den gjeldende minnesbruken kommer nær det forespurte minnet når programmet startet å kjøre. Det gir mening, men hvordan skal vi teste dette??

Jeg kommer absolutt ikke til å skrive et stykke kode for å fylle vår søknad med ubrukelige gjenstander til minnebruken blir for stor. I stedet bruker vi en funksjon som for øyeblikket ikke støttes av Adobe, ifølge Grant Skinner-artikkelen, som tvinger Garbage Collector til å kjøre. På den måten kan vi utløse denne enkle metoden og se hva som skjer når det går. Også fra nå av vil jeg referere til Garbage Collector som GC, for enkelhets skyld. Her er funksjonen:

 privat funksjon forceGC (e: MouseEvent): void prøv new LocalConnection (). connect ('foo'); nye LocalConnection (). connect ('foo');  catch (e: *)  // Viser en tekst i skjermbildetText ("----- Garbage collection triggered -----"); 

Denne enkle funksjonen, som bare oppretter to LocalConnection () objekter, er kjent for å tvinge GC til å kjøre, så vi vil ringe det når vi vil at dette skal skje. Jeg anbefaler ikke å bruke denne funksjonen i en seriøs applikasjon. Hvis du gjør det for test, er det ingen reelle problemer, men hvis det gjelder et program som blir distribuert til folk, er dette ikke en god funksjon å bruke, siden det kan medføre negative effekter.

Det jeg anbefaler for saker som dette er at du bare lar GC kjøre i sitt eget tempo. Ikke prøv å tvinge det. I stedet fokuserer på koding effektivt slik at minneproblemer ikke skjer (vi vil dekke dette i trinn 6). Nå, la oss se på vårt eksempel SWF igjen, og klikk på "Samle søppel" -knappen etter å ha opprettet og slettet en gjenstand.

Har du testet filen? Det funket! Du kan se at nå, etter å ha slettet et objekt og utløse GC, fjerner det objektet! Legg merke til at hvis du ikke sletter objektet og ringer til GC, skjer ingenting, siden det fortsatt er en referanse til objektet i koden. Nå, hva hvis vi prøver å holde to referanser til et objekt og fjerne en av dem?


Trinn 5: Opprette et annet referanse

Nå som vi har bevist at GC fungerer akkurat som ønsket, la oss prøve noe annet: lenke en annen referanse til et objekt (Object1) og fjern originalen. Først må vi opprette en funksjon for å koble sammen og koble fra en referanse til vårt objekt. La oss gjøre det:

 privat funksjon saveObject1 (e: MouseEvent): void // _onSave er en boolsk for å sjekke om vi skal koble eller oppheve referansen hvis (_onSave) // Hvis det ikke er noe objekt å lagre, gjør ingenting hvis (! _theObject1)  // Viser en tekst i skjermbildetText ("Det er ikke noe objekt å lagre!"); komme tilbake;  // En ny variabel for å holde en annen referanse til Object1 _theSavedObject = _theObject1; // Viser en tekst i skjermbildet Tekst ("Lagret objekt 1 vellykket!"); // På neste gang denne funksjonen kjører, kobler du fra den, siden vi bare har koblet _onSave = false;  else // Fjerner referansen til den _theSavedObject = null; // Viser en tekst i skjermbildet Tekst ("Ikke-lagret objekt 1 er vellykket!"); // På neste gang kjører denne funksjonen, kobler den, siden vi bare koblet fra _onSave = true; 

Hvis vi tester vår swf nå, vil vi legge merke til at hvis vi lager Object1, lagre det, slette det og tvinge GC til å løpe, vil ingenting skje. Det er fordi nå, selv om vi fjernet den "opprinnelige" lenken til objektet, er det fortsatt en annen referanse til den, noe som forhindrer at det er kvalifisert for søppelsamling. Dette er egentlig alt du trenger å vite om Garbage Collector. Det er jo ikke et mysterium. men hvordan skal vi bruke dette til vårt nåværende miljø? Hvordan kan vi bruke denne kunnskapen for å forhindre at søknaden vår går sakte? Dette er hva trinn 6 viser oss: hvordan du bruker dette i ekte eksempler.


Trinn 6: Gjør din kode effektiv

Nå for det meste: gjør koden din med GC effektivt! Dette trinnet vil gi nyttig informasjon som du bør beholde for hele livet ditt - lagre det riktig! Først vil jeg gjerne introdusere en ny måte å bygge objekter på i søknaden din. Det er en enkel, men effektiv måte å samarbeide med GC. Denne måten introduserer to enkle klasser, som kan utvides til andre, når du forstår hva det gjør.

Ideen med denne måten er å implementere en funksjon - kalt ødelegge () - på hvert objekt du lager, og ring det når du er ferdig med å jobbe med et objekt. Funksjonen inneholder all koden som er nødvendig for å fjerne alle referanser til og fra objektet (unntatt referansen som ble brukt til å ringe funksjonen), slik at du sørger for at objektet lar applikasjonen din helt isolert, og gjenkjennes lett av GC. Årsaken til dette forklares i neste trinn. La oss se på den generelle koden for funksjonen:

 // Opprett dette i hvert objekt du bruker offentlig funksjon ødelegge (): void // Fjern hendelseslyttere // Fjern alt i visningslisten // Fjern referansene til andre objekter, så det blir helt isolert // ... // Når du vil fjerne objektet, gjør du dette: theObject.destroy (); // Og så null den siste referansen til den theObject = null;

I denne funksjonen må du fjerne alt fra objektet, så det forblir isolert i applikasjonen. Etter å ha gjort det, vil det være lettere for GC å lokalisere og fjerne objektet. La oss nå se på noen av situasjonene der de fleste minnefeil skjer:

  • Objekter som bare brukes i et intervall for utførelse: vær forsiktig med disse, da de kan være de som bruker mye minne. Disse objektene eksisterer bare i en tidsperiode (for eksempel å lagre verdier når en funksjon kjører) og de blir ikke åpnet veldig ofte. Husk å fjerne alle referanser til dem etter at du er ferdig med dem, ellers kan du få mange av dem i søknaden din, bare ta minnesplass. Husk at hvis du lager mange referanser til dem, må du eliminere hver enkelt gjennom ødelegge() funksjon.
  • Objekter igjen i displaylisten: Fjern alltid et objekt fra visningslisten hvis du vil slette det. Skjermlisten er en av søppel innsamling røtter (husk det?), og så er det veldig viktig at du holder objekter dine unna når du fjerner dem.
  • Stage, foreldre og rot referanser: Hvis du liker å bruke mange disse egenskapene, husk å fjerne dem når du er ferdig. Hvis mange av objektene dine har en referanse til disse, kan du være i trøbbel!
  • Eventlyttere: Noen ganger referansen som holder objektene dine fra å bli samlet, er en hendelselytter. Husk å fjerne dem, eller bruk dem som svake lyttere, om nødvendig.
  • Arrays og vektorer: Noen ganger kan dine arrays og vektorer ha andre objekter, og etterlater referanser i dem som du kanskje ikke er klar over. Vær forsiktig med arrays og vektorer!

Trinn 7: Øya Referanser

Selv om det er bra å jobbe med GC, er det ikke perfekt. Du må være oppmerksom på hva du gjør, ellers kan dårlige ting skje med søknaden din. Jeg vil gjerne vise et problem som kan oppstå hvis du ikke følger alle nødvendige tiltak for å få koden til å fungere med GC riktig.

Noen ganger, hvis du ikke fjerner alle referanser til og fra et objekt, kan det hende du har dette problemet, spesielt hvis du knytter mange objekter sammen i søknaden din. Noen ganger kan en enkelt referanse igjen være nok til at dette skal skje: alle objektene dine danner en øye referanser, der alle objektene er koblet til andre, og ikke tillater at GC fjerner dem.

Når GC kjører, utfører det to enkle oppgaver for å kontrollere objekter som skal slettes. En av disse oppgavene teller hvor mange referanser hvert objekt har. Alle objekter med 0 referanser blir samlet inn samtidig. Den andre oppgaven er å kontrollere om det er en liten gruppe gjenstander som knytter til hverandre, men kan ikke nås, og dermed sløse bort minne. Sjekk bildet:

Som du kan se, kan de grønne objektene ikke nås, men deres referanse teller er 1. GC utfører den andre oppgaven for å sjekke om denne delen av objekter og fjerner dem alle. Men når klumpen er for stor, gir GC "opp" på kontrollen og antar at objektene kan nås. Forestill deg nå om du har noe sånt:

Dette er øya referanser. Det ville ta mye minne fra systemet, og vil ikke bli samlet av GC på grunn av kompleksiteten av det. Det høres ganske dårlig ut, va? Det kan imidlertid lett unngås, skjønt. Bare vær sikker på at du har ryddet hver referanse til og fra et objekt, og så vil skummelt ting som det ikke skje!


Konklusjon

Dette er det for nå. I denne Quick Tip lærte vi at vi kan gjøre koden bedre og mer effektiv for å redusere lag- og minneproblemer, noe som gjør den mer stabil. For å gjøre dette må vi forstå hvordan referanseobjekter fungerer i AS3, og hvordan å dra nytte av dem for å gjøre GC-arbeidet riktig i vår søknad. Til tross for at vi kan gjøre søknaden bedre, må vi være forsiktige når vi gjør det - ellers kan det bli enda mer messig og langsommere!

Jeg håper du likte dette enkle tipset. Hvis du har noen spørsmål, kan du slippe en kommentar nedenfor!