Questa traduzione è incompleta. Collabora alla traduzione di questo articolo dall’originale in lingua inglese.
Introduzione
Alcuni linguaggi di programmazione, come ad esempio C e C++, possiedono funzioni per la gestione della memoria a basso livello come malloc()
e free()
; in JavaScript l'allocazione e la de-allocazione della memoria (oggetti, stringhe, ecc.) avviene "automaticamente" grazie alla presenza del Garbage Collection. Il termine "automaticamente" è spesso fonte di confusione tra i programmatori JavaScript (e di altri linguaggi ad alto livello) poichè da l'impressione che si possa non preoccuparsi riguardo la gestione della memoria. Questo è un errore!
Ciclo di vita della memoria
A prescindere dal linguaggio di programmazione, il ciclo di vita della memoria è pressappoco sempre lo stesso:
- Allocazione della memoria necessaria
- Utilizzo della memoria (scrittura, lettura)
- Rilascio della memoria allocata quando non è più necessaria
I primi due punti sono espliciti in tutti i linguaggi; l'ultimo invece è esplicito nei linguaggi a basso livello, mentre il più delle volte implicito nei linguaggi ad alto livello come JavaScript.
Allocazione in JavaScript
Inizializzazione con valore
Per evitare che il programmatore debba allocare ogni variabile utilizzata, JavaScript lo fa contestualmente alla dichiarazione:
var n = 123; // alloca memoria per un numero var s = "qwerty"; // alloca memoria per una stringa var o = { a: 1, b: null }; // alloca memoria per un oggetto e i valori contenuti // (come un oggetto) alloca memoria per l'array e i valori contenuti var a = [1, null, "abra"]; function f(a){ return a + 2; } // alloca una funzione (cioè un oggetto invocabile) // istruzioni più complesse anche allocano un oggetto someElement.addEventListener('click', function(){ someElement.style.backgroundColor = 'blue'; }, false);
Allocazione via chiamata di funzione
La chiamata di alcune funzioni risultano nell'allocazione di un oggetto, come nel seguente esempio:
var d = new Date(); // alloca un DOM element var e = document.createElement('div');
Alcuni metodi allocano nuovi valori od oggetti:
var s = "qwerty"; var s2 = s.substr(0, 3); // s2 è una nuova stringa // Dato che le stringhe sono valori immutabili, // JavaScript può decidere di non allocare memoria per una nuova stringa, // ma semplicemente di salvare il range [0, 3]. var a = ["ouais ouais", "nan nan"]; var a2 = ["generation", "nan nan"]; var a3 = a.concat(a2); // Un nuovo array di 4 elementi, la concatenazione degli elementi di a e a2
Utilizzo dei valori
Usare i valori in sostanza significa accedere alla memoria allocata. Questo può essere fatto leggendo o scrivendo il valore di una variabile, di una proprietà di un oggetto, o anche semplicemente passando un argomento a una funzione.
Rilascio della memoria quando non più necessaria
La maggior parte dei problemi riguardo la gestione della memoria arrivano in questa fase; L'impresa più difficile è determinare quando "la memoria allocata non è più necessaria". Ciò si traduce per il programmatore in individuare dove, nel codice, questo pezzo di memoria non risulta più necessario, e quindi liberarlo.
I linguaggi ad alto livello hanno una funzionalità chiamata "Garbage Collector" il cui compito è di seguire l'allocazione e l'uso della memoria così da determinare quando una porzione della memoria allocata non è più necessaria e liberarla automaticamente. Questo processo è un approssimazione dato che il problema generico di conoscere se una porzione di memoria serve o no è irrisolvibile (non può essere risolto da un algoritmo).
Garbage Collection
Partendo dal presupposto che il generico problema di sapere dove una determinata porzione di memoria "non serve più" è irrisolvibile, come conseguenza, l'implementazione dei garbege collection sono una restrizione della soluzione del problema generico. Questa sezione illustra le nozioni necessarie a capire il funzionamento dei principali algoritmi che implementano i garbage collection e le loro limitazioni.
Referenze
L'idea principale su cui si basano gli algoritmi di garbage collection sono le referenze. Nel contesto della gestione della memoria, un oggetto è referente di un altro se può accedere a quest'ultimo (sia in modo implicito che esplicito). Ad esempio, un oggetto JavaScript è referente del suo prototype (referenza implicita) e ai valori delle sue proprietà (referenza esplicita).
In questo contesto, il concetto di "oggetto" si estende a qualcosa di molto più ampio rispetto al tradizionale concetto di "oggetto JavaScript" e contiene function scopes (or the global lexical scope).
Reference-counting garbage collection
Quello qui descritto è l'algoritmo di garbage collection più semplice. Esso riduce il problema da "quando un oggetto non serve più" a "quando un oggetto non è più referenziato da nessun altro oggetto". Un oggetto è considerato garbage collectable (letteralmente, spazzatura da raccogliere) se nessun altro oggetto ha almeno una referenza ad esso.
Esempio
var o = { a: { b:2 } }; // Alloca 2 oggetti. Il primo è referenziato dall'altro come sua proprietà. // L'altro è referenziato dalladalla virtù di essere assegnato allavariabile 'o'. // Ovviamente, nessuno dei due può essere cancellato per liberare memoria var o2 = o; // la variabile 'o2' è il secondo oggetto // che referenzia lo stesso oggetto di 'o' o = 1; // ora l'oggetto che originariamente era in 'o' ha un'unica referenza // rappresentata dalla variabile 'o2' var oa = o2.a; // referenza alla proprietà 'a' dell'oggetto. // Quest'oggetto ora ha 2 referenze: una come proprietà di 'o2' // e una come variabile 'oa' o2 = "yo"; // L'oggetto che originariamente era in 'o' adesso non // più alcuna referenza e può essere cancellato. // Però la sua proprietà 'a' è ancora referenziata // dall'oggetto 'oa', quindi non può essere ancora eliminato oa = null; // la proprietà 'a' del precedente oggetto adesso non ha // alcuna referenza. Può essere cancellata.
Limitazione: cicli di referenze
Questo algotitmo ha una limitazione quando entra in un ciclo di referenze. Nell'esempio seguente sono creati due oggetti che si referenziano a vicenda, uno referenzia l'altro e viceversa, creando un ciclo di referenze (dipendenza ciclica). In questo modo ogni oggetto ha almento una referenza, l'algoritmo del reference-countig (ovvero quello che "conta" quante referenze ha un oggetto) da come risultato che nessuno dei due oggetti può essere cancellato (non sono garbage-collactable).
function f(){ var o = {}; var o2 = {}; o.a = o2; // o referenzia o2 o2.a = o; // o2 referenzia o return "qwerty"; } f(); // Two objects are created and reference one another thus creating a cycle. // They will not get out of the function scope after the function call, so they // are effectively useless and could be free'd. // However, the reference-counting algorithm considers that since each of both // object is referenced at least once, none can be garbage-collected.
Esempio reale
Internet Explorer 6 e 7 sono conosciuti per avere un reference-counting garbage collector per gli oggetti DOM. I cicli di referenze sono errori comuni che possono generare uno spreco di memoria (memory leaks).
var div;
window.onload = function(){
div = document.getElementById("myDivElement");
div.circularReference = div;
div.lotsOfData = new Array(10000).join("*");
};
Nell'esempio precedente, l'elemento "myDivElement" ha una referenza circolare con se stesso nella proprietà "circularReference". Se la proprietà non è esplicitamente rimossa o annullata, un reference-counting garbage collector avrà sempre almeno una referenza intatta che manterrà l'elemento in memoria anche se esso fosse rimosso dal DOM. Se un oggetto trattiene molta memoria e ha almeno una referenza (come illustrato nell'esempio precedente con la proprietà "lotsOfData") la memoria da esso occupata non sarà mai rilasciata.
L'algoritmo mark-and-sweep
Questo algoritmo è il più famoso degli algoritmi di tracing; la tecnica del tracing è concettualmente molto semplice e si basa sulla definizione "quando un oggetto diventa irraggiungibile".
Essa consiste nel prendere come riferimento gli oggetti root (indice dell'albero) e, partendo da essi, mediante una ricorsione, marcare (tramite un bit o una mappa distinta) tutti gli oggetti collegati tra loro da un riferimento. Al termine di questa operazione, gli oggetti raggiungibili saranno marcati mentre quelli non raggiungibili saranno eliminati.
L'algoritmo mark-and-sweep (marca e butta via) si suddivide in due fasi: la prima fase (mark) nella quale l'algoritmo marca tutti gli oggetti che hanno almeno un riferimento attivo; nella seconda fase (sweep) nella quale tutti gli oggetti non marcati vengono liberati e la memoria viene restituita.
A differenza della tecnica del reference counting il cui il costo dell'algoritmo è proporzionale all'ammontare del lavoro eseguito dal programma (ogni volta che si alloca/dealloca un oggetto bisogna creare-incrementare/decrementare il contatore), la tecnica del tracing è proporzionale alla dimensione della memoria ed è sicuramente più efficiente.
I cicli non sono più un problema
Nel primo esempio, dopo che la funzione ha restituito un risultato, i 2 oggetti non sono più referenziati da nessun altro oggetto raggiungibile dall'oggetto globale (root). Ne consegue che saranno irraggiungibili dal garbage collector e quindi eleminati.
La stessa cosa accade con il secondo esempio. Una volta che il div e la sua variabile sono diventati irraggiungibili dalle radici, essi possono essere cancellati nonostante abbiano una referenza ciascuno.
Limitazione: gli oggetti devono essere esplicitamente irragiungibili
Sebbene sia marcata come una limitazione, è raramente raggiunta in pratica ed è anche il motivo per cui nessuno si preoccupa, di solito, dei garbage collector.
Piccolo appunto italiano
Tutte le parole
tagliatesono state tradotte letteralmente ma in modo che la frase non perda di significato.cit. Il traduttore