mercoledì 11 febbraio 2015

Terminare Finalmente

Chi proviene dal C++ sa bene che è molto importante rilasciare la memoria allocata precedentemente nello heap. Le classi C++ hanno il distruttore proprio per questo motivo.
Potrebbe sembrare che il metodo finalizer di Java sia l'equivalente del distruttore in C++.

NON LO E' !!!

Il metodo finalizer di Java è da usare soltanto in due casi:

  1. Come rete di protezione per assicurarsi che una risorsa nativa sia stata rilasciata e per questo scopo è stato creato un metodo apposito che l'oggetto client si potrebbe essere dimenticato di chiamare.
  2. Per rilasciare delle risorse native non critiche, cioè risorse la cui occupazione non deve essere rilasciata il prima possibile. Infatti se una classe Java chiama una libreria nativa, il garbage collector non può intervenire sulla parte nativa, quando rilascia l'oggetto in Java, quindi il finalizer in questo caso ha senso, però il programmatore potrebbe comunque fare un metodo di rilascio e chiamarlo.


Il metodo finalizer è chiamato dal Garbage Collector, quindi non c'è la certezza di quando è chiamato: dipende da quale algoritmo di GC è usato e dalla particolare implementazione della virtual machine.
La virtual machine potrebbe anche terminare senza che il finalizer sia mai stato chiamato.
I metodi finalizer rallentano il programma.

Il modo corretto di rilasciare le risorse critiche è un try finally, non il finalizer !

Anche se si chiama System.runFinalization non si ha la garanzia che il finalizer sia chiamato, mentre il System.runFinalizerOnExit e il Runtime.runFinalizerOnExit sono stati deprecati.

Il finalizer pone problemi anche con l'overriding di una sottoclasse, nel caso in cui il programmatore distratto si sia dimenticato di chiamare il super.finalizer() nella sottoclasse, a tal proposito ha senso definire nella superclasse un oggetto come proprietà privata, il quale deve avere il finalizer per liberare le risorse, in modo che, se anche la sottoclasse non chiama il super.finalizer(), quando interviene il garbage collector, questi chiami automaticamente il finalizer dell'oggetto privato, non si sa quando.

Una curiosità per i programmatori C++: la parentesi graffa chiusa } è un operatore nel mondo C++, quindi se abbiamo aperto un file e abbiamo il riferimento nello stack, la graffa chiusa chiama il metodo close() !!!

Questo in Java non succede, perché la parentesi graffa indica soltanto la fine del blocco di codice, si deve ricorrere al try finally o al try-with-resources che accetta oggetti chiudibili e si occupa di chiuderli automaticamente alla fine del blocco try, altra dimostrazione che il finalizer è da evitare come la peste, perché il modo giusto è ricorrere il più possibile al try.

domenica 1 febbraio 2015

Dove non arriva lo spazzino

Uno degli elementi che piace di più ai giovani programmatori e a ai manager anziani riguardo a Java è il Garbage Collector: uno spazzino che arriva a liberare la memoria, che noi programmatori abbiamo allocato in modo automatico, trovando tutto ciò comodissimo.
Invito il lettore a parlare con qualsiasi sistemista che si occupi di monitorare l'occupazione di memoria della macchina e a chiedergli se anche lui (o lei) è d'accordo sulle meraviglie del Garbage Collector (GC).
Il GC è un'arma a doppio taglio, si tratta di un algoritmo complesso, che è una delle prime cause della lentezza di Java, inoltre è un invito per i programmatori per diventare pigri.
Ci sono casi infatti dove la disabitudine dei programmatori Java a occuparsi della memoria, dando fiducia completa al Garbage Collector, portano a dei memory leack.

Si possono individuare 3 casi dove il programmatore Java deve prestare attenzione, per non rendere impossibile il lavoro del GC.

1) Raggiungere l'insufficienza di memoria tramite la ritenzione non intenzionale di oggetti. Se si ritengono dei riferimenti a degli oggetti, per esempio in un vettore o in uno stack dove si desidera rimpicciolire la dimensione della struttura e non si pone a null il valore degli elementi rimasti fuori dal limite abbassato, succede che tali riferimenti impediranno al Garbage Collector di liberare la memoria degli oggetti riferiti.

2) Le cache devono liberare gli elementi più vecchi. E' comune tenere in cache dei riferimenti a degli oggetti in cache e dimenticarsi poi di togliere tali riferimenti, quando questi non sono più usati da tanto tempo. Una buona struttura per realizzare una cache in Java è il WeakHashMap. Nel caso invece dove si scelga una LinkedHashMap, esiste un metodo molto interessante: removeEldestEntry che toglie l'elemento più vecchio, magari si può realizzare un Timer o uno ScheduledThreadPoolExecutor che periodicamente lo chiama per pulire la cache degli elementi più vecchi.

3) I listener sono spesso usati per le applicazioni desktop. Richiedono molta attenzione perché i listener vanno tolti dalla lista dei listener registrati, quando non servono più, perché troppo spesso i listener rimangono e cresce il numero di riferimenti a oggetti, compromettendo l'occupazione efficiente della memoria.

Questi punti si trovano descritti sempre nell'ottimo Effective Java di Joshua Bloch.