CIAO!

Penso che non rimarrai troppo sorpreso se ti dico che il tuo computer ha una quantità di memoria limitata :) Anche un disco rigido, generalmente molte volte più grande della memoria RAM, può essere riempito al massimo con i tuoi giochi preferiti, programmi TV, e altro ancora. Per evitare che ciò accada, è necessario monitorare lo stato corrente della memoria ed eliminare i file non necessari dal computer. Cosa c'entra la programmazione Java con tutto questo? Qualunque cosa! Dopotutto, quando la macchina Java crea un oggetto, alloca memoria per quell'oggetto.

In un programma davvero grande, vengono create decine e centinaia di migliaia di oggetti e ognuno di essi ha il proprio pezzo di memoria allocato per esso. Ma da quanto tempo pensi che esistano tutti questi oggetti? "Vivono" per tutto il tempo in cui il nostro programma è in esecuzione? Ovviamente no. Anche con tutti i vantaggi degli oggetti Java, non sono immortali :) Gli oggetti hanno il loro ciclo di vita. Oggi ci prenderemo una piccola pausa dalla scrittura del codice e daremo un'occhiata a questo processo :) Inoltre, è molto importante per capire come funziona un programma e come vengono gestite le risorse. Quindi, quando inizia la vita di un oggetto? Come una persona - dalla sua nascita, cioè creazione.


Cat cat = new Cat(); // Here the lifecycle of our Cat object begins!

Innanzitutto, la Java Virtual Machine alloca la quantità di memoria richiesta per creare l'oggetto. Quindi crea un riferimento a quel ricordo. Nel nostro caso, quel riferimento si chiama cat, quindi possiamo tenerne traccia. Quindi tutte le sue variabili vengono inizializzate, viene chiamato il costruttore e — ta-da! — il nostro oggetto appena coniato vive di vita propria :)

La durata della vita degli oggetti varia, quindi non possiamo fornire numeri esatti qui. In ogni caso, vive per qualche tempo all'interno del programma e svolge le sue funzioni. Per essere precisi, un oggetto è "vivo" finché ci sono riferimenti ad esso. Non appena non ci sono più riferimenti, l'oggetto "muore". Esempio:


public class Car {
  
   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");
       lamborghini = null;

   }

}

L'oggetto auto Lamborghini Diablo smette di essere vivo sulla seconda riga del main()metodo. C'era solo un riferimento ad esso, e quindi quel riferimento è stato impostato uguale a null. Poiché non rimangono riferimenti alla Lamborghini Diablo, la memoria allocata diventa "spazzatura". Un riferimento non deve essere impostato su null affinché ciò accada:


public class Car {

   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");

       Car lamborghiniGallardo = new Car("Lamborghini Gallardo");
       lamborghini = lamborghiniGallardo;
   }

}

Qui abbiamo creato un secondo oggetto e quindi assegnato questo nuovo oggetto al lamborghiniriferimento. Ora l' Lamborghini Gallardooggetto ha due riferimenti, ma l' Lamborghini Diablooggetto non ne ha nessuno. Ciò significa che l' Diablooggetto ora è spazzatura. Questo è quando entra in gioco il meccanismo integrato di Java chiamato Garbage Collector (GC).

Il Garbage Collector è un meccanismo Java interno responsabile della liberazione della memoria, ovvero della rimozione dalla memoria di oggetti non necessari. C'è una buona ragione per cui abbiamo scelto l'immagine di un robot aspirapolvere qui. Dopotutto, il garbage collector funziona più o meno allo stesso modo: in background, "viaggia" intorno al tuo programma, raccogliendo spazzatura praticamente senza alcuno sforzo da parte tua. Il suo compito è rimuovere gli oggetti che non sono più utilizzati nel programma.

In questo modo si libera memoria nel computer per altri oggetti. Ricordi che all'inizio della lezione abbiamo detto che nella vita ordinaria devi monitorare lo stato del tuo computer ed eliminare i file non necessari? Bene, nel caso di oggetti Java, il Garbage Collector lo fa per te. Il Garbage Collector viene eseguito ripetutamente durante l'esecuzione del programma: non è necessario chiamarlo esplicitamente o impartirgli comandi, sebbene ciò sia tecnicamente possibile. Più avanti ne parleremo più approfonditamente e analizzeremo il suo lavoro in modo più dettagliato.

Quando il Garbage Collector raggiunge un oggetto, appena prima di distruggerlo, chiama un metodo speciale — finalize()— sull'oggetto. Questo metodo può rilasciare altre risorse utilizzate dall'oggetto. Il finalize()metodo fa parte della Objectclasse. Ciò significa che oltre ai equals()metodi hashCode()e toString()incontrati in precedenza, ogni oggetto ha questo metodo. Si differenzia dagli altri metodi in quanto è - come dovrei dire - molto capriccioso.

In particolare, non sempre viene chiamato prima della distruzione di un oggetto. La programmazione è uno sforzo preciso. Il programmatore dice al computer di fare qualcosa e il computer lo fa. Suppongo che tu sia già abituato a questo comportamento, quindi all'inizio potrebbe essere difficile per te accettare questa idea seguente: "Prima della distruzione degli oggetti, viene chiamato il metodo della classe. finalize()O Objectforse non viene chiamato. Tutto dipende da la tua fortuna!"

Eppure, è vero. La macchina Java stessa determina se chiamare o meno il finalize()metodo caso per caso. Ad esempio, proviamo a eseguire il seguente codice come esperimento:


public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public Cat() {
   }

   public static void main(String[] args) throws Throwable {
       for (int i = 0 ; i < 1000000; i++) {
           Cat cat = new Cat();
           cat = null; // This is when the first object becomes available to the garbage collector
       }
   }

   @Override
   protected void finalize() throws Throwable {
       System.out.println("Cat object destroyed!");
   }
}

Creiamo un Catoggetto e poi nella successiva riga di codice impostiamo il suo unico riferimento uguale a null. E lo facciamo un milione di volte. Abbiamo sovrascritto esplicitamente il finalize()metodo in modo che stampi una stringa sulla console un milione di volte (una volta per ogni volta che distrugge un Catoggetto). Ma no! Per essere precisi, è stato eseguito solo 37.346 volte sul mio computer! Cioè, solo una volta su 27 la macchina Java installata sulla mia macchina ha deciso di chiamare il finalize()metodo.

In altri casi, la raccolta dei rifiuti è avvenuta senza di essa. Prova a eseguire questo codice per te stesso: molto probabilmente otterrai un risultato diverso. Come puoi vedere, finalize()difficilmente può essere definito un partner affidabile :) Quindi, un piccolo consiglio per il futuro: non affidarti al finalize()metodo per liberare risorse critiche. Forse la JVM lo chiamerà, o forse no. Chi lo sa?

Se mentre il tuo oggetto è vivo contiene alcune risorse che sono estremamente importanti per le prestazioni, ad esempio una connessione al database aperta, è meglio creare un metodo speciale nella tua classe per rilasciarle e poi chiamarlo esplicitamente quando l'oggetto non è più necessario. In questo modo saprai per certo che le prestazioni del tuo programma non ne risentiranno. Fin dall'inizio abbiamo detto che lavorare con la memoria e rimuovere la spazzatura è molto importante, e questo è vero. La gestione impropria delle risorse e il fraintendimento del modo in cui gli oggetti non necessari vengono eliminati possono causare perdite di memoria. Questo è uno degli errori di programmazione più noti.

Se i programmatori scrivono il loro codice in modo errato, ogni volta potrebbe essere allocata nuova memoria per gli oggetti appena creati, mentre gli oggetti vecchi e non necessari potrebbero non essere disponibili per la rimozione da parte del Garbage Collector. Dato che abbiamo fatto un'analogia con un robot aspirapolvere, immagina cosa succederebbe se, prima di avviare il robot, spargessi calzini per casa, rompi un vaso di vetro e lasci un mattoncino Lego su tutto il pavimento. Il robot proverà a fare il suo lavoro, ovviamente, ma a un certo punto si bloccherà.

Per consentire al robot aspirapolvere di funzionare correttamente, è necessario mantenere il pavimento in buone condizioni e rimuovere tutto ciò che il robot non è in grado di gestire. Lo stesso principio si applica al Garbage Collector di Java. Se in un programma sono rimasti molti oggetti che non possono essere ripuliti (come un calzino o un mattoncino Lego per il nostro robot aspirapolvere), a un certo punto la memoria si esaurirà. E potrebbe non essere solo il tuo programma a bloccarsi: ogni altro programma in esecuzione sul computer potrebbe essere interessato. Anche loro potrebbero non avere abbastanza memoria.

Non è necessario memorizzarlo. Hai solo bisogno di capire il principio alla base di come funziona.