Gestionarea memoriei Java, cu colectarea de gunoi încorporată, este una dintre cele mai bune realizări ale limbajului. Acesta permite dezvoltatorilor să creeze obiecte noi fără a se preocupa în mod explicit de alocarea și dezalocarea memoriei, deoarece colectorul de gunoi recuperează automat memoria pentru reutilizare. Acest lucru permite o dezvoltare mai rapidă, cu mai puțin cod de tip boilerplate, eliminând în același timp scurgerile de memorie și alte probleme legate de memorie. Cel puțin în teorie.
În mod ironic, colectorul de gunoi Java pare să funcționeze prea bine, creând și eliminând prea multe obiecte. Majoritatea problemelor de gestionare a memoriei sunt rezolvate, dar adesea cu prețul creării unor probleme serioase de performanță. A face garbage collection adaptabilă la tot felul de situații a dus la un sistem complex și greu de optimizat. Pentru a vă face o idee despre colectarea gunoiului, trebuie mai întâi să înțelegeți cum funcționează gestionarea memoriei într-o mașină virtuală Java (JVM).
Cum funcționează cu adevărat colectarea gunoiului în Java
Mulți oameni cred că colectarea gunoiului colectează și elimină obiectele moarte. În realitate, colectarea gunoiului Java face exact opusul! Obiectele vii sunt urmărite și tot restul desemnat gunoi. După cum veți vedea, această neînțelegere fundamentală poate duce la multe probleme de performanță.
Să începem cu heap, care este zona de memorie utilizată pentru alocarea dinamică. În majoritatea configurațiilor, sistemul de operare alocă în avans heap-ul pentru a fi gestionat de JVM în timp ce programul rulează. Acest lucru are câteva ramificații importante:
- Crearea obiectelor este mai rapidă deoarece nu este necesară sincronizarea globală cu sistemul de operare pentru fiecare obiect în parte. O alocare revendică pur și simplu o anumită porțiune dintr-o matrice de memorie și mută pointerul de offset înainte (a se vedea figura 2.1). Următoarea alocare începe de la acest decalaj și revendică următoarea porțiune din matrice.
- Când un obiect nu mai este utilizat, colectorul de gunoi revendică memoria de bază și o reutilizează pentru alocări viitoare de obiecte. Acest lucru înseamnă că nu există o ștergere explicită și nu se returnează memorie sistemului de operare.
Figura 2.1: Obiectele noi sunt pur și simplu alocate la sfârșitul heap-ului utilizat.
Toate obiectele sunt alocate pe zona heap gestionată de JVM. Fiecare obiect pe care dezvoltatorul îl folosește este tratat în acest mod, inclusiv obiectele de clasă, variabilele statice și chiar codul însuși. Atâta timp cât un obiect este referit, JVM îl consideră viu. Odată ce un obiect nu mai este referit și, prin urmare, nu mai este accesibil codului aplicației, colectorul de gunoi îl elimină și revendică memoria neutilizată. Oricât de simplu ar suna, acest lucru ridică o întrebare: care este prima referință din arbore?
Rădăcini ale colecției de gunoi – Sursa tuturor arborilor de obiecte
Care arbore de obiecte trebuie să aibă unul sau mai multe obiecte rădăcină. Atâta timp cât aplicația poate ajunge la aceste rădăcini, întregul arbore este accesibil. Dar când sunt considerate accesibile acele obiecte rădăcină? Obiectele speciale numite rădăcini de colectare a gunoiului (garbage-collection roots (GC roots; a se vedea figura 2.2) sunt întotdeauna accesibile și la fel orice obiect care are o rădăcină de colectare a gunoiului la propria rădăcină.
Există patru tipuri de rădăcini GC în Java:
- Variabilele locale sunt menținute în viață de către stiva unui fir de execuție. Aceasta nu este o referință virtuală a unui obiect real și, prin urmare, nu este vizibilă. În toate scopurile, variabilele locale sunt rădăcini GC.
- Firele Java active sunt întotdeauna considerate obiecte vii și, prin urmare, sunt rădăcini GC. Acest lucru este deosebit de important pentru variabilele locale ale firelor.
- Variabilele statice sunt referite prin clasele lor. Acest fapt le face de facto rădăcini GC. Clasele însele pot fi colectate din gunoi, ceea ce ar elimina toate variabilele statice referite. Acest lucru are o importanță deosebită atunci când folosim servere de aplicații, containere OSGi sau încărcătoare de clase în general. Vom discuta problemele aferente în secțiunea Modele de probleme.
- Referințele JNI sunt obiecte Java pe care codul nativ le-a creat ca parte a unui apel JNI. Obiectele astfel create sunt tratate în mod special, deoarece JVM nu știe dacă este sau nu referit de codul nativ. Astfel de obiecte reprezintă o formă foarte specială de rădăcină GC, pe care o vom examina mai detaliat în secțiunea Modele de probleme de mai jos.
Figura 2.2: Rădăcinile GC sunt obiecte care sunt ele însele referite de JVM și, astfel, împiedică orice alt obiect să fie colectat la gunoi.
Prin urmare, o aplicație Java simplă are următoarele rădăcini GC:
- Variabilele locale din metoda main
- Hirul principal
- Variabilele statice ale clasei main
Marcarea și măturarea gunoiului
Pentru a determina ce obiecte nu mai sunt utilizate, JVM execută intermitent ceea ce se numește în mod foarte potrivit un algoritm de marcare și măturare. După cum ați putea intui, este un proces simplu, în doi pași:
- Algoritmul traversează toate referințele obiectelor, începând cu rădăcinile GC, și marchează fiecare obiect găsit ca fiind în viață.
- Toată memoria heap care nu este ocupată de obiectele marcate este recuperată. Ea este pur și simplu marcată ca fiind liberă, în esență este măturată de obiectele nefolosite.
Colectarea gunoiului este menită să elimine cauza scurgerilor clasice de memorie: obiectele din memorie care nu se poate ajunge, dar care nu sunt eliminate. Cu toate acestea, acest lucru funcționează numai pentru scurgerile de memorie în sensul original. Este posibil să existe obiecte nefolosite care sunt încă accesibile de către o aplicație pentru că dezvoltatorul a uitat pur și simplu să le dereferențieze. Astfel de obiecte nu pot fi colectate din gunoaie. Chiar mai rău, o astfel de scurgere de memorie logică nu poate fi detectată de niciun software (a se vedea figura 2.3). Chiar și cel mai bun software de analiză nu poate decât să evidențieze obiectele suspecte. Vom examina analiza scurgerilor de memorie în secțiunea Analiza impactului asupra performanței al utilizării memoriei și al colectării gunoiului, de mai jos.
.