Memory leak
# Memory leak

In informatica, un memory leak ("perdita o fuoriuscita di memoria") è un particolare tipo di consumo non voluto di memoria dovuto alla mancata deallocazione dalla stessa di variabili o dati non più utilizzati da parte dei processi.

Come spiegato sotto, un memory leak ha sintomi simili a quelli di altri problemi, e generalmente può essere diagnosticato solo da un programmatore che ha accesso al codice sorgente. Molte persone tendono ad indicare qualsiasi aumento involontario del consumo di memoria come un memory leak, anche se non è proprio la stessa cosa.

## Conseguenze di un memory leak

Un memory leak può peggiorare le prestazioni del computer riducendo la quantità di memoria libera disponibile. L'allocazione di memoria viene di solito gestita dal sistema operativo, cosicché il risultato di un memory leak è un aumento dell'occupazione di memoria dell'intero sistema e non solo del programma che lo ha creato. Nel caso peggiore, troppa memoria viene sprecata dai leak e il sistema può smettere di funzionare correttamente.

I leak possono non essere gravi o rintracciabili con mezzi normali. Nei moderni sistemi operativi, la memoria usata da un processo viene automaticamente rilasciata al termine del processo, ciò significa che un memory leak causato da un programma che resta in esecuzione per poco tempo raramente è grave.

I casi in cui un leak diventa serio comprendono:

* quando il programma è lasciato in esecuzione e consuma memoria continuamente (come lavori eseguiti in background, su server o su sistemi embedded lasciati in esecuzione per vari anni);
* quando il programma è capace di richiedere memoria (come la memoria condivisa) non rilasciata anche quando il programma termina;
* quando il leak viene creato all'interno del sistema operativo;
* quando la memoria è molto limitata.

## Un semplice esempio

Questo esempio vuole dimostrare come un leak può nascere, ed i suoi effetti, senza dover conoscere le basi della programmazione. Questo è solo un esempio fittizio.

Il programma in questione fa parte di un software molto semplice dedicato al controllo di un ascensore. Questa porzione di algoritmo viene eseguita ogni volta che qualcuno all'interno preme un bottone.

Quando il bottone viene premuto:

* recupera un po' di memoria per ricordare il piano richiesto
* metti il numero richiesto in memoria
* siamo già al piano giusto?
* se sì, non dobbiamo fare niente: termino la procedura adesso
* altrimenti, vai al piano richiesto
* rilascia la memoria usata per ricordare il numero del piano

Questo programma potrebbe sembrare corretto, ma contiene un leak. Consideriamo il caso in cui l'ascensore si trova al piano 3 e premiamo il bottone 3. Otteniamo un po' di memoria che non restituiremo mai. Ogni volta che succede perdiamo un po' di memoria.

Questo problema non avrà un effetto immediato. Le persone non premono il bottone di un piano su cui stanno, ed in ogni caso ci può essere abbastanza memoria da gestire questa situazione centinaia o migliaia di volte. Ma alla fine la memoria finirà. Potrebbe richiedere mesi o anni, o potrebbe non essere mai scoperto.

Le conseguenze potrebbero essere sgradevoli; alla fine l'ascensore smetterebbe di funzionare. Se il programma avesse bisogno di memoria per aprire le porte, qualcuno potrebbe restare intrappolato all'interno, visto che non abbiamo risorse per aprirle.

Bisogna notare che il memory leak aumenta finché il programma è in esecuzione. Ad esempio, se un calo di elettricità blocca l'ascensore, al ritorno dell'alimentazione la memoria sarà completamente disponibile ed il lento processo di perdita di memoria deve ricominciare da zero.

## Problemi di programmazione

I memory leak sono errori comuni nella programmazione, specialmente se si utilizzano linguaggi di programmazione che non hanno un garbage collection automatico, ad esempio il C o il C++. Tipicamente, questo errore si presenta quando un'area di memoria allocata dinamicamente è diventata irraggiungibile.

I linguaggi di programmazione che forniscono una gestione automatica della memoria, come Java, C# o Lisp, non sono immuni dai memory leak. Nonostante il memory manager possa recuperare la memoria irraggiungibile (e quindi inutile) non può liberare la memoria che ancora può essere raggiunta (e quindi è potenzialmente utile). I più moderni collector permettono al programmatore di marcare la memoria con vari livelli di utilità, corrispondenti a vari livelli di raggiungibilità. Il manager non libera memoria se è fortemente raggiungibile. Un oggetto viene detto fortemente raggiungibile se è linkato direttamente da una strong reference o indirettamente da una catena di strong reference (una strong reference è una reference che, a differenza di una weak, evita che un oggetto venga cancellato dal garbage collector). Per evitare questo tipo di memory leak, lo sviluppatore è responsabile della pulizia della memoria dopo l'utilizzo, solitamente settando la reference a NULL quando non è più necessaria.

In linea di massima, la gestione automatica è molto più robusta e conveniente per gli sviluppatori visto che non devono creare un sistema alternativo. È più semplice per un programmatore sapere quando una reference non è più necessaria piuttosto che sapere quando un oggetto non è più referenziato. Bisogna però sottolineare che il sistema automatico, oltre a non eliminare tutti gli errori, obbliga il sistema ad un ulteriore lavoro.

## Effetti di un memory leak

Se un programma ha un memory leak ed il suo uso di memoria viene continuamente aumentato, non ci saranno sintomi immediati. Quasi tutti i sistemi hanno una certa quantità di memoria disponibile che può essere assegnata ai processi. Alla fine la memoria RAM disponibile può esaurirsi. Questo potrebbe portare a due effetti:

* Nei sistemi in cui tutta la memoria è RAM c'è un immediato fallimento.
* Molti sistemi operativi moderni usano il disco fisso per fornire memoria virtuale. Quando la RAM finisce, viene aumentato l'uso del disco fisso. Anche il disco fisso può finire lo spazio, ma prima di arrivare a quel punto il programma sarà talmente lento che verrà considerato fallito in anticipo.
* Alcuni sistemi operativi moderni implementano delle euristiche che decidono, qualora le risorse disponibili stiano per terminare (come ad esempio, appunto, la memoria), quale sia il processo migliore da terminare per evitare di incorrere in un fallimento. In questo caso, è possibile che l'applicazione che soffre di questo tipo di errore di programmazione venga automaticamente terminata, senza avere effetti drastici sulla vita complessiva del sistema.

Se un sistema va in crash per le continue richieste di memoria, probabilmente siamo davanti ad un bug o perlomeno ad un design scadente. Quello che normalmente succede è che il programma alla fine chiederà memoria che gli verrà negata, o perché è terminata o per aver raggiunto il proprio limite. Quello che succede a questo punto dipende dal programma:

* termina, probabilmente con un messaggio d'errore.
* tenta di autoripararsi. Di solito questo tentativo fallisce perché necessita di memoria. A volte le applicazioni partono riservandosi un po' di memoria proprio per evitare questi problemi.
* continua l'esecuzione come se niente fosse. Questo porterà ad un access violation rovinando informazioni di questa o, in sistemi primitivi, di altre applicazioni.

## È un memory leak?

L'aumento continuo di memoria utilizzata non indica necessariamente un leak. Alcuni programmi accumulano informazioni in memoria (cache). Se la cache cresce tanto da creare problemi, può indicare errori di design, ma non memory leak visto che quella memoria è in realtà raggiungibile. In altri casi il programma potrebbe aver bisogno di una grande quantità di memoria perché il programmatore l'ha ritenuta sufficiente per un particolare task: ad esempio, un programma grafico potrebbe caricarsi in memoria un intero file di immagine.

Per dirlo con altre parole, un memory leak nasce da un particolare tipo di errore di programmazione e, senza avere accesso al codice, si può solo dire che "potrebbe" essere un leak. È meglio usare un termine come "aumento continuo di uso di memoria" se non si è sicuri della causa. Tuttavia, esistono dei tool come valgrind che permettono di analizzare il programma mentre è in esecuzione tramite l'utilizzo di speciali librerie modificate al posto di quelle standard: simili tool consentono l'individuazione di memory leak senza analizzare staticamente il codice sorgente.

Il termine memory leak è forte ed i non-programmatori potrebbero usarlo per indicare errori non relativi alla memoria come i buffer overrun.

## Semplice esempio di memoria irraggiungibile in C

Presentiamo una funzione C che, volontariamente, causa dei leak. Poiché il programma cicla in eterno chiamando la funzione errata, alla fine creerà il problema.

Occorre notare come, nel precedente esempio, la memoria allocata viene effettivamente acceduta in scrittura. Questo è un passo necessario per consentire, su sistemi moderni, il reale verificarsi del problema.

Infatti, la libreria per l'allocazione della memoria malloc interagisce direttamente con il gestore della memoria del sistema operativo sul quale il programma è in esecuzione. I moderni sistemi operativi interpretano l'invocazione della libreria malloc come una richiesta di preriservamento di indirizzi virtuali e non di memoria fisica. La memoria fisica corrispondente a quegli indirizzi virtuali verrà effettivamente allocata soltanto al primo accesso in scrittura.

Pertanto, omettere un accesso alla memoria riservata tramite chiamata a malloc non permette al software di far manifestare il fenomeno del memory leak, almeno fintanto che il sistema operativo avrà a disposizione degli indirizzi virtuali liberi in cui preriservare la richiesta di memoria. Dato che nei moderni sistemi a 64 bit lo spazio di indirizzamento è molto grande, questo fenomeno potrebbe richiedere una quantità di tempo non indifferente per portare ad un reale arresto dell'applicazione.

## Esempi reali di memory leak

### Gmail

Gmail ha 2 interfacce: la HTML4.1 e la preferenziale HTML5, più ricca di funzioni, tra cui l'aggiornamento automatico della cassetta postale

La modalità HTML5 usa uno script SOAP, in cui un bug crea un leak in maniera assolutamente casuale su Mozilla Firefox in ambiente Linux Mint ed assorbe tutta la RAM disponibile, bloccando il calcolatore. Se si ha la fortuna di avere la scheda in cui risiede Gmail a portata di cursore lo si può terminare, altrimenti, dato che in quelle condizioni il mouse è lentissimo e risponde male, si è costretti a disattivare l'intero browser.

### VLC media player

Un bug nel player avviene nella versione 2.2.5.1 Linux quando il lettore ricarica in loop un filmato rinominato, assorbendo tutta la potenza di calcolo disponibile di tutte le CPU ed a volte saturando la memoria.

### Mozilla Firefox

La versione 65 del browser ha un modulo denominato "Electrolysis", destinato a far risparmiar tempo in fase di rinfresco dell'intero gruppo di schede non destinato all'aggiornamento riversandone l'intero contenuto in RAM.

Il browser non controlla il numero delle schede coinvolto e se se ne hanno troppe aperte dalla sessione precedente il sistema si blocca praticamente in modo assoluto. Per esempio, se si hanno circa 300 schede aperte su un PC con 4GB di memoria il sistema comincerà a mostrare la pagina selezionata ed a non essere più saturo dopo 3h 43min, e comunque il calcolatore non sarà responsivo non avendo più memoria disponibile (il 91.3% di RAM ed il 90.4% di Swap sono impegnati) vanificando l'intero target del progetto Electrolysis.

