Salut!

Je pense que vous ne serez pas trop surpris si je vous dis que votre ordinateur a une quantité de mémoire limitée :) Même un disque dur - généralement plusieurs fois plus grand que le stockage RAM - peut être rempli à pleine capacité avec vos jeux préférés, émissions de télévision, et plus. Pour éviter que cela ne se produise, vous devez surveiller l'état actuel de la mémoire et supprimer les fichiers inutiles de votre ordinateur. Qu'est-ce que la programmation Java a à voir avec tout cela ? Tout! Après tout, lorsque la machine Java crée un objet, elle alloue de la mémoire pour cet objet.

Dans un vrai grand programme, des dizaines et des centaines de milliers d'objets sont créés, et chacun d'eux a sa propre mémoire qui lui est allouée. Mais combien de temps pensez-vous que tous ces objets existent ? Est-ce qu'ils "vivent" pendant toute la durée de notre programme ? Bien sûr que non. Même avec tous les avantages des objets Java, ils ne sont pas immortels :) Les objets ont leur propre cycle de vie. Aujourd'hui, nous allons faire une petite pause dans l'écriture du code et examiner ce processus :) De plus, il est très important pour votre compréhension du fonctionnement d'un programme et de la gestion des ressources. Alors, quand commence la vie d'un objet ? Comme une personne — depuis sa naissance, c'est-à-dire sa création.


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

Tout d'abord, la machine virtuelle Java alloue la quantité de mémoire requise pour créer l'objet. Ensuite, il crée une référence à cette mémoire. Dans notre cas, cette référence s'appelle cat, nous pouvons donc en garder une trace. Ensuite, toutes ses variables sont initialisées, le constructeur est appelé et — ta-da ! — notre nouvel objet vit sa propre vie :)

La durée de vie des objets varie, nous ne pouvons donc pas fournir de chiffres exacts ici. Dans tous les cas, il vit pendant un certain temps à l'intérieur du programme et remplit ses fonctions. Pour être précis, un objet est "vivant" tant qu'il y a des références à lui. Dès qu'il n'y a plus de références, l'objet "meurt". Exemple:


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'objet voiture Lamborghini Diablo cesse d'être vivant sur la deuxième ligne de la main()méthode. Il n'y avait qu'une seule référence, puis cette référence était égale à null. Puisqu'il n'y a plus de références à la Lamborghini Diablo, la mémoire allouée devient "ordures". Une référence n'a pas besoin d'être définie sur null pour que cela se produise :


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;
   }

}

Ici, nous avons créé un deuxième objet, puis attribué ce nouvel objet à la lamborghiniréférence. Maintenant, l' Lamborghini Gallardoobjet a deux références, mais l' Lamborghini Diabloobjet n'en a aucune. Cela signifie que l' Diabloobjet est maintenant une poubelle. C'est à ce moment que le mécanisme intégré de Java appelé le ramasse-miettes (GC) entre en jeu.

Le ramasse-miettes est un mécanisme Java interne chargé de libérer de la mémoire, c'est-à-dire de supprimer les objets inutiles de la mémoire. Il y a une bonne raison pour laquelle nous avons choisi ici une photo d'un robot aspirateur. Après tout, le ramasse-miettes fonctionne à peu près de la même manière : en arrière-plan, il "parcourt" votre programme, récupérant les ordures sans pratiquement aucun effort de votre part. Son travail consiste à supprimer les objets qui ne sont plus utilisés dans le programme.

Cela libère de la mémoire dans l'ordinateur pour d'autres objets. Vous souvenez-vous qu'au début de la leçon, nous avons dit que dans la vie ordinaire, vous devez surveiller l'état de votre ordinateur et supprimer les fichiers inutiles ? Eh bien, dans le cas des objets Java, le ramasse-miettes le fait pour vous. Le ramasse-miettes s'exécute à plusieurs reprises pendant l'exécution de votre programme : vous n'avez pas à l'appeler explicitement ou à lui donner des commandes, bien que cela soit techniquement possible. Plus tard, nous en parlerons davantage et analyserons son travail plus en détail.

Lorsque le ramasse-miettes atteint un objet, juste avant de détruire l'objet, il appelle une méthode spéciale — finalize()— sur l'objet. Cette méthode peut libérer d'autres ressources utilisées par l'objet. La finalize()méthode fait partie de la Objectclasse. Cela signifie qu'en plus des méthodes equals(), hashCode()et toString()que vous avez rencontrées précédemment, chaque objet possède cette méthode. Elle diffère des autres méthodes en ce qu'elle est — comment dire — très capricieuse.

En particulier, il n'est pas toujours appelé avant la destruction d'un objet. La programmation est un effort précis. Le programmeur dit à l'ordinateur de faire quelque chose, et l'ordinateur le fait. Je suppose que vous êtes déjà habitué à ce comportement, donc au début il vous sera peut-être difficile d'accepter cette idée suivante : "Avant la destruction d'objets, la finalize()méthode de la Objectclasse est appelée. Ou peut-être qu'elle n'est pas appelée. Tout dépend de ta chance !"

Pourtant, c'est vrai. La machine Java détermine elle-même s'il faut ou non appeler la finalize()méthode au cas par cas. Par exemple, essayons d'exécuter le code suivant à titre expérimental :


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!");
   }
}

Nous créons un Catobjet, puis dans la ligne de code suivante, nous définissons sa seule référence égale à null. Et nous le faisons un million de fois. Nous avons explicitement remplacé la finalize()méthode afin qu'elle affiche une chaîne sur la console un million de fois (une fois pour chaque fois qu'elle détruit un Catobjet). Mais non! Pour être précis, il n'a été exécuté que 37 346 fois sur mon ordinateur ! Autrement dit, une seule fois sur 27, la machine Java installée sur ma machine a décidé d'appeler la finalize()méthode.

Dans d'autres cas, la collecte des ordures s'est déroulée sans elle. Essayez d'exécuter ce code par vous-même : vous obtiendrez très probablement un résultat différent. Comme vous pouvez le voir, finalize()peut difficilement être qualifié de partenaire fiable :) Alors, un petit conseil pour l'avenir : ne comptez pas sur la finalize()méthode pour libérer des ressources critiques. Peut-être que la JVM l'appellera, ou peut-être pas. Qui sait?

Si, pendant que votre objet est vivant, il contient des ressources extrêmement importantes pour les performances, par exemple une connexion ouverte à la base de données, il est préférable de créer une méthode spéciale dans votre classe pour les libérer, puis de l'appeler explicitement lorsque l'objet n'est plus nécessaire. De cette façon, vous saurez avec certitude que les performances de votre programme ne souffriront pas. Dès le début, nous avons dit qu'il était très important de travailler avec la mémoire et de supprimer les ordures, et c'est vrai. Une mauvaise gestion des ressources et une mauvaise compréhension de la manière dont les objets inutiles sont nettoyés peuvent entraîner des fuites de mémoire. C'est l'une des erreurs de programmation les plus connues.

Si les programmeurs écrivent leur code de manière incorrecte, une nouvelle mémoire peut être allouée à chaque fois aux objets nouvellement créés, tandis que les anciens objets inutiles peuvent ne pas être disponibles pour être supprimés par le ramasse-miettes. Puisque nous avons fait une analogie avec un robot aspirateur, imaginez ce qui se passerait si, avant de démarrer le robot, vous éparpilliez des chaussettes dans la maison, cassiez un vase en verre et laissiez un bloc de construction Lego partout sur le sol. Le robot essaiera de faire son travail, bien sûr, mais à un moment donné, il se bloquera.

Pour permettre à l'aspirateur robot de fonctionner correctement, vous devez maintenir le sol en bon état et enlever tout ce que le robot ne peut pas manipuler. Le même principe s'applique au ramasse-miettes de Java. S'il reste de nombreux objets dans un programme qui ne peuvent pas être nettoyés (comme une chaussette ou un bloc de construction Lego pour notre robot aspirateur), à un moment donné, vous manquerez de mémoire. Et ce n'est peut-être pas seulement votre programme qui se bloquera - tous les autres programmes exécutés sur l'ordinateur peuvent être affectés. Eux aussi peuvent ne pas avoir assez de mémoire.

Vous n'avez pas besoin de mémoriser cela. Vous avez juste besoin de comprendre le principe de son fonctionnement.