Înțelegerea colecției de gunoi în AS3

Ați folosit vreodată o aplicație Flash și ați observat o întârziere în ea? Încă nu știu de ce jocul flash se desfășoară încet pe calculatorul tău? Dacă doriți să aflați mai multe despre o posibilă cauză a acesteia, atunci acest articol este pentru dvs..

Am gasit acest autor minunat datorita FlashGameLicense.com, locul unde puteti cumpara si vinde jocuri Flash!

Tutorial publicat

La fiecare câteva săptămâni, revizuim câteva postări preferate ale cititorului nostru de-a lungul istoriei site-ului. Acest tutorial a fost publicat pentru prima oară în iunie 2010.


Rezultatul final al rezultatelor

Să aruncăm o privire asupra rezultatului final pe care îl vom strădui:


Pasul 1: O rulare rapidă prin trimitere

Înainte de a intra în subiectul real, trebuie mai întâi să știți un pic despre modul în care funcționează instanțierea și referința în AS3. Dacă ați citit deja despre aceasta, vă recomand să citiți acest pas mic. Astfel, toate cunoștințele vor fi proaspete în capul tău și nu vei avea probleme să citești restul acestui sfat rapid!

Crearea și referința instanțelor din AS3 este diferită de cea a majorității oamenilor. Instanția (sau "crearea") a ceva se întâmplă numai atunci când codul cere să creeze un obiect. De obicei, acest lucru se întâmplă prin cuvântul cheie "nou", dar este, de asemenea, prezent când utilizați a literal sintaxă sau definiți parametrii pentru funcții, de exemplu. Exemple de acest lucru sunt prezentate mai jos:

 // Instanțierea prin noul cuvânt cheie nou Obiect (); Array nou (); int int (); String nou (); nou Boolean (); data nouă (); // Instanțierea prin sintaxă literală ; []; 5 "Bună ziua!" true // Instanțierea prin parametrii funcției funcția privată tutExample (parametrul1: int, parametrul2: Boolean): void

După ce un obiect este creat, acesta va rămâne singur până când ceva îl va face referire. Pentru a face acest lucru, în general, creați o variabilă și transmiteți valoarea obiectului la variabila, astfel încât să știe ce obiect deține în prezent. Cu toate acestea (și aceasta este partea pe care majoritatea oamenilor nu o cunoaște), când treceți valoarea unei variabile la o altă variabilă, nu creați un obiect nou. În schimb, creați un alt link spre obiectul pe care ambele variabile dețin acum! Vedeți imaginea de mai jos pentru clarificări:

Imaginea presupune ambele Variabila 1 și Variabila 2 pot deține zâmbetul (adică pot deține același tip). În partea stângă, numai Variabila 1 există. Cu toate acestea, atunci când creăm și stabilim Variabila 2 la aceeași valoare Variabila 1, nu creăm o legătură între Variabila 1 și Variabila 2 (partea din dreapta sus a imaginii), în schimb creăm o legătură între Smiley și Variabila 2 (partea din dreapta jos a imaginii).

Cu aceste cunoștințe, putem să mergem la colectorul de gunoi.


Pasul 2: Fiecare oraș are nevoie de un colector de gunoi

Este evident că fiecare aplicație are nevoie de o anumită cantitate de memorie pentru a rula, deoarece are nevoie de variabile pentru a ține valori și a le folosi. Ceea ce nu este clar este modul în care aplicația gestionează obiectele care nu mai sunt necesare. Îi reciclează? Îl ștergeți? Părăsește obiectul în memorie până când aplicația este închisă? Toate cele trei opțiuni se pot întâmpla, dar aici vom vorbi în mod specific despre al doilea și al treilea.

Imaginați-vă o situație în care o aplicație creează o mulțime de obiecte atunci când este inițializată, dar odată ce această perioadă se termină, mai mult de jumătate din obiectele create rămân neutilizate. Ce s-ar întâmpla dacă ar fi fost lăsate în memorie? Cu siguranță, ar lua mult spațiu în el, provocând astfel ceea ce numesc oamenii întârziere, care este o încetinire considerabilă în aplicație. Cei mai mulți utilizatori nu ar prefera acest lucru, așa că trebuie să evităm acest lucru. Cum putem codifica pentru ca aplicația să funcționeze mai eficient? Răspunsul este în Colector de gunoi.

Colectorul de gunoi este o formă de gestionare a memoriei. Scopul său este de a elimina orice obiect care nu este folosit și ocupă spațiu în memoria sistemului. În acest fel, aplicația poate rula cu utilizarea memoriei mimimum. Să vedem cum funcționează:

Când cererea dvs. începe să ruleze, cere o cantitate de memorie din sistem care va fi utilizată de aplicație. Aplicația începe apoi să completeze această memorie cu orice informație de care aveți nevoie; fiecare obiect pe care îl creați intră în el. Cu toate acestea, dacă utilizarea memoriei se apropie de memoria solicitată inițial, Garbage Collector rulează, căutând orice obiect care nu este folosit pentru a goli spațiu în memorie. Uneori, acest lucru cauzează un pic de întârziere în aplicație, datorită cheltuielilor mari de căutare a obiectelor.

În imagine, puteți vedea vârfuri de memorie (înconjurat cu verde). Vârfurile și căderea bruscă sunt cauzate de colectorul de gunoi, care acționează atunci când aplicația a atins consumul de memorie solicitat (linia roșie), eliminând toate obiectele inutile.


Pasul 3: Pornirea fișierului SWF

Acum că știm ce poate face colecționarul de gunoi pentru noi, este timpul să învățăm cum să codificăm pentru a obține toate avantajele de la acesta. În primul rând, trebuie să știm cum funcționează Colectorul de gunoi, într-o perspectivă practică. În cod, obiectele devin eligibile pentru colecția de gunoi atunci când devin inaccesibile. Când un obiect nu poate fi accesat, codul înțelege că nu va mai fi folosit, deci trebuie colectat.

Actionscript 3 verifică disponibilitatea prin gunoi de colectare rădăcini. În momentul în care un obiect nu poate fi accesat printr-o rădăcină de colectare a gunoiului, acesta devine eligibil pentru colectare. Mai jos vedeți o listă a principalelor rădăcini de colectare a gunoiului:

  • Variabile la nivel de pachete și statice.
  • Variabile și variabile locale în scopul unei metode sau al unei funcții de execuție.
  • Variabile de instanță din instanța principală a clasei aplicației sau din lista de afișare.

Pentru a înțelege cum sunt tratate obiectele de către Colectorul de gunoi, trebuie să codificăm și să examinăm ce se întâmplă în fișierul exemplu. Voi folosi proiectul ASD3 al lui FlashDevelop și compilatorul lui Flex, dar presupun că îl puteți face pe orice IDE doriți, deoarece nu vom folosi anumite lucruri care există doar în FlashDevelop. Am construit un fișier simplu cu un buton și o structură de text. Deoarece acest lucru nu este obiectivul acestui sfat rapid, îl voi explica rapid: când se face clic pe un buton, o funcție se declanșează. În orice moment vrem să afișăm un text pe ecran, apelați o funcție cu textul și este afișat. Există, de asemenea, un alt câmp de text pentru a afișa o descriere a butoanelor.

Obiectivul exemplului nostru este de a crea obiecte, de a le șterge și de a examina ce se întâmplă cu ele după ce sunt șterse. Vom avea nevoie de o modalitate de a ști dacă obiectul este viu sau nu, așa că vom adăuga un ascultător ENTER_FRAME la fiecare dintre obiecte și le vom afișa un text cu timpul în care au trăit. Deci, să codificăm primul obiect!

Am creat o imagine amuzantă de zâmbet pentru obiecte, în tribut adus lui Michael James Williams, marele joc tutorial Avoider, care folosește imagini zâmbitoare. Fiecare obiect va avea un număr pe cap, astfel încât îl putem identifica. De asemenea, am numit primul obiect TheObject1, și al doilea obiect TheObject2, astfel încât va fi ușor de distins. Să mergem la cod:

 privat var _theObject1: TheObject1; funcția privată newObjectSimple1 (e: MouseEvent): void // Dacă există deja un obiect creat, nu faceți nimic dacă (_theObject1) se întoarce; // Creați noul obiect, setați-l la poziția în care ar trebui să fie și adăugați-l în lista de afișare pentru a vedea că a fost creat _theObject1 = new TheObject1 (); _theObject1.x = 320; _theObject1.y = 280; _theObject1.addEventListener (Event.ENTER_FRAME, changeTextField1); addChild (_theObject1); 

Al doilea obiect pare aproape același. Aici este:

 privat var _theObject2: TheObject2; funcția privată newObjectSimple2 (e: MouseEvent): void // Dacă există deja un obiect creat, nu faceți nimic dacă (_theObject2) se întoarce; // Creați noul obiect, setați-l la poziția în care ar trebui să fie și adăugați-l în lista de afișare pentru a vedea că a fost creat _theObject2 = new TheObject2 (); _theObject2.x = 400; _theObject2.y = 280; _theObject2.addEventListener (Event.ENTER_FRAME, changeTextField2); addChild (_theObject2); 

În cod, newObjectSimple1 () și newObjectSimple2 () sunt funcții care sunt declanșate la apăsarea butonului corespunzător. Aceste funcții creează pur și simplu un obiect și îl adaugă pe ecran, așa că știm că a fost creat. În plus, creează un ENTER_FRAME ascultător de evenimente în fiecare obiect, ceea ce îi va face să afișeze un mesaj în fiecare secundă, atâta timp cât acestea sunt active. Iată funcțiile:

 funcția privată changeTextField1 (e: Event): void // Exemplul nostru rulează la 30FPS, deci să adăugăm 1/30 pe fiecare cadru din contor. _objectCount1 + = 0,034; // Verifică dacă _objectCount1 a trecut încă o secundă dacă (int (_objectCount1)> _secondCount1) // Afișează un text pe ecranul afișatText ("Obiect 1 este viu ..." + int (_objectCount1)); _secondCount1 = int (_obiectCount1); 
 funcția privată changeTextField2 (e: Event): void // Exemplul nostru rulează la 30FPS, deci să adăugăm 1/30 pe fiecare cadru din contor. _objectCount2 + = 0,034; // Verifică dacă _objectCount2 a trecut o încă o secundă dacă (int (_objectCount2)> _secondCount2) // Afișează un text în ecranul afișatText ("Obiect 2 este viu ..." + int (_objectCount2)); _secondCount2 = int (_obiectCount2); 

Aceste funcții afișează pur și simplu un mesaj pe ecran cu timpul în care obiectele au fost în viață. Aici este fișierul SWF cu exemplul curent:


Pasul 4: Ștergerea obiectelor

Acum că am acoperit crearea obiectelor, să încercăm ceva: ați întrebat vreodată ce s-ar întâmpla dacă ștergeți (eliminați toate referințele) un obiect? Se colectează gunoi? Asta vom testa acum. Vom construi două butoane de ștergere, câte unul pentru fiecare obiect. Să facem codul pentru ei:

 funcția privată deleteObject1 (e: MouseEvent): void // Verificați dacă _theObject1 există într-adevăr înainte de al scoate din lista de afișare dacă (_theObject1 && conține (_theObject1)) removeChild (_theObject1); // Eliminarea tuturor referințelor la obiect (aceasta este singura referință) _theObject1 = null; / / Afișează un text pe ecranul ecranuluiText ("Șterse obiectul 1 cu succes!"); 
 funcția privată deleteObject2 (e: MouseEvent): void // Verificați dacă _theObject2 există într-adevăr înainte de al scoate din lista de afișare dacă (_theObject1 && conține (_theObject2)) removeChild (_theObject2); // Eliminarea tuturor referințelor la obiect (aceasta este singura referință) _theObject2 = null; // Afișează un text pe ecranul afișatText ("Șterse obiectul 2 cu succes!"); 

Să aruncăm o privire la SWF acum. Ce crezi ca se va intampla?

După cum puteți vedea. Dacă faceți clic pe "Creați Obiect1" și apoi pe "Ștergeți Obiect1", nimic nu se întâmplă cu adevărat! Putem spune că codul rulează, deoarece textul apare pe ecran, dar de ce nu se șterge obiectul? Obiectul este încă acolo pentru că nu a fost eliminat. Când am șters toate referirile la aceasta, am spus codul să o facă eligibil pentru colectarea gunoiului, dar colectorul de gunoi nu rulează niciodată. Amintiți-vă că colectorul de gunoi va rula numai atunci când utilizarea curentă a memoriei se apropie de memoria solicitată când aplicația a început să funcționeze. Are sens, dar cum vom încerca acest lucru?

Cu siguranță nu voi scrie o bucată de cod pentru a umple aplicația noastră cu obiecte inutile până când utilizarea memoriei devine prea mare. În schimb, vom folosi o funcție nesuportată în prezent de Adobe, conform articolului Grant Skinner, care obligă colectorul de gunoi să ruleze. În acest fel, putem declanșa această metodă simplă și putem vedea ce se întâmplă atunci când rulează. De asemenea, de acum înainte, mă refer la Garbage Collector ca GC, de dragul simplității. Iată funcția:

 funcția privată a funcțieiGC (e: MouseEvent): void try new LocalConnection () conectați ("foo"); noul LocalConnection () conectați ("foo");  captură (e: *)  // Afișează un text pe ecranul ecranuluiText ("----- Colecția de gunoi declanșată -----"); 

Această funcție simplă, care creează doar două obiecte LocalConnection (), este cunoscută pentru a forța GC să ruleze, așa că o vom numi când vrem să se întâmple acest lucru. Nu recomand utilizarea acestei funcții într-o aplicație serioasă. Dacă o faci pentru testare, nu există probleme reale, dar dacă este vorba despre o aplicație care va fi distribuită oamenilor, aceasta nu este o funcție bună de folosit, deoarece poate avea efecte negative.

Ceea ce vă recomand pentru astfel de cazuri este că ați lăsat GC să meargă în propriul ritm. Nu încerca să-l forțezi. În schimb, concentrați-vă pe codificarea eficientă, astfel încât problemele de memorie să nu se întâmple (vom acoperi acest lucru în pasul 6). Acum, să aruncăm o privire la exemplul SWF din nou și să dăm clic pe butonul "Collect Garbage" după crearea și ștergerea unui obiect.

Ai testat dosarul? A mers! Puteți vedea că acum, după ștergerea unui obiect și declanșarea GC, acesta elimină obiectul! Observați că dacă nu ștergeți obiectul și nu sunați la GC, nimic nu se va întâmpla, deoarece există încă o referință la acest obiect în cod. Acum, dacă încercăm să păstrăm două referințe la un obiect și să eliminăm unul dintre ele?


Pasul 5: Crearea unei alte referințe

Acum, că am dovedit că GC funcționează exact așa cum ne-am dorit, să încercăm altceva: să conectăm o altă referință la un obiect (Object1) și să eliminăm originalul. În primul rând, trebuie să creăm o funcție pentru a lega și a deconecta o referință la obiectul nostru. S-o facem:

 Funcția privată saveObject1 (e: MouseEvent): void // _onSave este un boolean pentru a verifica dacă trebuie să conectăm sau să deconectăm referința dacă (_onSave) // Dacă nu există niciun obiect de salvat, nu face nimic dacă ! _theObject1)  // Afișează un text pe ecranul ecranuluiText ("Nu există nici un obiect 1 pentru a salva!"); întoarcere;  // O nouă variabilă care să dețină o altă referință la Object1 _theSavedObject = _theObject1; // Afișează un text pe ecranul ecranuluiText ("Obiect salvat cu succes!"); // La următoarea funcționare a acestei funcții, deconectați-o, deoarece am legat doar _onSave = false;  altceva // Eliminarea referinței la acesta _theSavedObject = null; / / Afișează un text pe ecranul ecranuluiText ("Un nesavariat obiect 1 cu succes!"); // La următoarea dată când această funcție rulează, conectați-o, deoarece am deconectat _onSave = true; 

Dacă vom testa acum swf-ul nostru, vom observa că dacă vom crea Object1, apoi vom salva, vom șterge și vom forța GC să ruleze, nimic nu se va întâmpla. Asta pentru că acum, chiar dacă am eliminat legătura "originală" cu obiectul, există încă o referință la acesta, ceea ce îl împiedică să fie eligibil pentru colectarea gunoiului. Acesta este, în principiu, tot ce trebuie să știți despre colectorul de gunoi. În definitiv, nu este un mister. dar cum să aplicăm acest lucru mediului nostru actual? Cum putem folosi aceste cunoștințe pentru a împiedica ca aplicația noastră să ruleze încet? Acesta este pasul 6 care ne va arăta: cum să aplicăm acest lucru în exemple reale.


Pasul 6: Efectuarea codului dvs. eficient

Acum, pentru cea mai bună parte: a face codul să funcționeze eficient cu GC! Acest pas va furniza informații utile pe care ar trebui să le păstrați pentru întreaga viață - salvați-le în mod corespunzător! În primul rând, aș dori să introduc o nouă modalitate de a vă construi obiectele în aplicația dvs. Este o modalitate simplă, dar eficientă de a colabora cu GC. Acest mod introduce două clase simple, care pot fi extinse la alții, odată ce înțelegeți ce face.

Ideea acestui mod este de a implementa o funcție - numită destru () - pe fiecare obiect pe care îl creați și îl numiți de fiecare dată când terminați lucrul cu un obiect. Funcția conține tot codul necesar pentru a elimina toate referințele la și de la obiect (cu excepția referinței care a fost folosită pentru a apela funcția), deci asigurați-vă că obiectul lasă aplicația dvs. total izolată și este ușor de recunoscut de GC. Motivul pentru aceasta este explicat în pasul următor. Să ne uităm la codul general al funcției:

 // Creați acest lucru în fiecare obiect pe care îl utilizați funcția publică distruge (): void // Eliminați ascultătorii evenimentului // Eliminați orice din lista de afișare // Ștergeți referințele la alte obiecte, deci devine total izolată // ... // Când doriți să eliminați obiectul, faceți acest lucru: theObject.destroy (); // Și apoi null ultima referință la ea theObject = null;

În această funcție, va trebui să ștergeți totul de la obiect, astfel încât să rămână izolat în aplicație. După aceasta, va fi mai ușor ca GC să localizeze și să înlăture obiectul. Acum, să examinăm câteva dintre situațiile în care se întâmplă majoritatea erorilor de memorie:

  • Obiecte care sunt utilizate numai într-un interval de execuție: aveți grijă cu acestea, deoarece acestea pot fi cele care consumă o mulțime de memorie. Aceste obiecte există doar pentru o anumită perioadă de timp (de exemplu, pentru a stoca valori atunci când o funcție rulează) și nu sunt accesate foarte des. Nu uitați să eliminați toate referințele la acestea după ce ați terminat cu acestea, altfel puteți avea multe dintre ele în aplicație, luând doar spațiu de memorie. Rețineți că, dacă creați multe referințe la acestea, trebuie să le eliminați pe fiecare distruge() funcţie.
  • Obiectele rămase în lista de afișare: eliminați întotdeauna un obiect din lista de afișare dacă doriți să îl ștergeți. Lista de afișare este una din gunoi de colectare rădăcini (amintiți-vă că?) și astfel este foarte important să vă păstrați obiectele departe de ea atunci când le îndepărtați.
  • Etape, părinte și referințe rădăcină: dacă doriți să utilizați mult aceste proprietăți, amintiți-vă să le eliminați când ați terminat. Dacă multe dintre obiectele dvs. au o referință la acestea, este posibil să aveți probleme!
  • Ascultători de evenimente: uneori referința care ține obiectele de la colectare este un ascultător al evenimentului. Amintiți-vă să le eliminați sau să le folosiți ca ascultători slabi, dacă este necesar.
  • Arrays și vectori: uneori, matricele și vectorii dvs. pot avea alte obiecte, lăsând referințe în interiorul lor, pe care probabil că nu le cunoașteți. Fii atent cu array-uri și vectori!

Pasul 7: Insula referințelor

Deși lucrul cu GC este minunat, nu este perfect. Trebuie să acorzi atenție ceea ce faci, altfel lucrurile rele se pot întâmpla în cazul aplicației. Aș dori să demonstrez o problemă care ar putea apărea dacă nu urmați toți pașii necesari pentru a face codul să funcționeze corespunzător cu GC.

Uneori, dacă nu ștergeți toate referințele la și de la un obiect, este posibil să aveți această problemă, mai ales dacă conectați o mulțime de obiecte împreună în aplicația dvs. Uneori, o singură referință stânga poate fi suficientă pentru ca acest lucru să se întâmple: toate obiectele dvs. formează o insulă de referințe, în care toate obiectele sunt conectate la alții, permițând GC să le elimine.

Când rulează GC, execută două sarcini simple pentru a verifica dacă obiectele trebuie șterse. Una dintre aceste sarcini numără câte referințe are fiecare obiect. Toate obiectele cu referințe 0 sunt colectate în același timp. Cealaltă sarcină este de a verifica dacă există o mică grămadă de obiecte care se leagă unul de celălalt, dar nu pot fi accesate, pierzând astfel memoria. Verificați imaginea:

După cum puteți vedea, obiectele verzi nu pot fi accesate, dar numărarea lor de referință este 1. GC efectuează a doua sarcină pentru a verifica această bucată de obiecte și a le elimina pe toate. Cu toate acestea, atunci când bucata este prea mare, GC "renunță" la verificare și presupune că obiectele pot fi atinse. Acum imaginați-vă dacă aveți ceva de genul:

Aceasta este insula referințelor. Ar fi nevoie de o mulțime de memorie din sistem și nu ar fi colectat de GC din cauza complexității acestuia. Suna destul de rău, nu? Cu toate acestea, poate fi ușor evitată. Asigurați-vă că ați îndepărtat fiecare referință la și de la un obiect, iar atunci lucrurile înfricoșătoare nu se vor întâmpla!


Concluzie

Aceasta este pentru moment. În acest sfat rapid am aflat că putem face codul nostru mai bun și mai eficient pentru a reduce problemele de întârziere și memorie, făcându-l mai stabil. Pentru a face acest lucru, trebuie să înțelegem cum funcționează referirea obiectelor în AS3 și cum să beneficieze de ele pentru a face GC să funcționeze corect în aplicația noastră. În ciuda faptului că putem face ca cererea noastră să fie mai bună, trebuie să fim atenți atunci când o facem - în caz contrar, poate deveni chiar mai confuză și mai înceată!

Sper că ți-a plăcut acest sfat simplu. Dacă aveți întrebări, renunțați la un comentariu de mai jos!

Cod