Récemment, j'ai un travail lié à un processus Java qui utilise beaucoup de mémoire, et j'avais besoin de connaître GC, je vais donc résumer ce que j'ai étudié. C'est une époque où je cherche, mais je l'ai écrit pour organiser mes pensées. Je vous serais reconnaissant de bien vouloir signaler qu'il est possible que vous vous mépreniez.
Remarque: cet article ne concerne pas les dernières affaires du GC, mais la méthode classique.
JVM Tout d'abord, du mécanisme de base de Java. Les processus Java s'exécutent sur des machines virtuelles appelées JVM. Ce mécanisme fonctionne sur différents OS et permet d'exécuter du code Java compilé (fichiers de classe) dans différents environnements sans se soucier des différences d'environnement.
Il existe plusieurs types de JVM, mais cet article suppose la VM HotSpot utilisée par Open JDK. (Je ne connais pas la différence avec les autres JVM)
La mémoire est allouée lorsque vous démarrez un processus Java. La zone qui est principalement utilisée est appelée zone de tas, et les données qui changent dynamiquement au cours du processus d'exécution du traitement sont essentiellement allouées ici. Il est important d'optimiser l'utilisation de la mémoire dans cette zone de tas pour les applications qui s'exécutent dans le même processus pendant une longue période.
Lors de la spécification de la taille à allouer, spécifiquement, en option
-Xms20m -Xmx100m
Si vous spécifiez Taille initiale de la mémoire allouée à la zone de tas: 20 Mo Taille maximale de la mémoire allouée à la zone de tas: 100 Mo
Ce sera le décor.
Si vous créez une application sans connaître l'état d'utilisation de la mémoire, les données inutiles (garbage) qui ne sont plus utilisées seront créées.
Dans cet article, je présenterai un exemple d'occurrence de garbage d'une manière facile à comprendre. https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-MA16-GC.pdf
Par exemple, si vous avez la classe suivante
class TreeNode {
public TreeNode left, right;
public int data;
TreeNode(TreeNode l, TreeNode r, int d) {
left = l; right = r; data = d;
}
public void setLeft(TreeNode l) { left = l;}
public void setRight(TreeNode r) {right = r;}
}
Créez un TreeNode par le processus suivant.
TreeNode left = new TreeNode(null, null, 13);
TreeNode right = new TreeNode(null, null, 19);
TreeNode root = new TreeNode(left, right, 17);
Cela signifie que le nœud racine fait référence aux nœuds gauche et droit.
Supposons que vous ayez ajouté un processus pour remplacer le bon nœud.
root.setRight(new TreeNode(null, null, 21));
Ensuite, le 19e nœud, qui était à l'origine dans le nœud droit, ne sera référencé par personne et sera dans l'état indiqué dans la figure ci-dessous.
Dans cet état, l'instance TreeNode avec data = 19 est un objet qui n'est référencé par personne, donc il est devenu une poubelle.
Si des données inutilisées continuent d'être générées, la mémoire inutile continuera à s'accumuler et la capacité atteindra finalement sa limite. Afin d'éviter cela, GC (Garbage Collection) est requis comme mécanisme pour libérer automatiquement la mémoire gaspillée dans la zone de tas.
Comme mentionné précédemment, GC est un mécanisme pour libérer de la mémoire inutile. Il examine les données dans la mémoire, les laisse comme données valides s'il y a une référence, et les libère s'il n'y a pas de référence, jugeant qu'elle n'est pas nécessaire. Cependant, il est inefficace de simplement scruter tout l'espace mémoire, il est donc géré en interne en fonction de la période d'existence des données.
Les données jeunes s'appellent Young Generation, les anciennes données s'appellent Old Generation et les données connues pour être moins susceptibles de changer sont appelées génération permanente.
Fondamentalement, l'allocation de mémoire se produit fréquemment, mais la plupart d'entre elles ne vivent pas longtemps, nous séparons donc les nouvelles données (jeune génération) des données de référence à long terme (ancienne génération). Cela permet de vérifier efficacement uniquement les données contenues dans Young Generation en ciblant le GC.
Il existe également une zone appelée Génération permanente, qui stocke des informations sur les classes chargées qui sont garanties de rester les mêmes dans une certaine mesure.
Référence: Ouvrir la documentation JDK
Il existe plusieurs algorithmes GC.
Etc.
Cette fois, je vais expliquer la méthode utilisée dans Serial GC et Parallel GC.
La zone Young de la zone de tas est divisée en Eden et Survivor comme indiqué dans la figure ci-dessous, et GC est effectué en utilisant bien chaque zone.
Chaque zone a les rôles suivants.
Eden Première zone mémoire allouée
Survivor1, Survivor2 Données qui ne sont pas publiées après GC et qui ne vont pas à Old (pour plus de commodité, seuls 1 et 2 sont ajoutés aux deux types)
Tenured Chose ancienne. Les données qui survivent après avoir expérimenté GC un nombre spécifié de fois sont migrées vers Old
Un GC qui ne cible que la jeune génération est appelé un GC mineur. Il présente les caractéristiques suivantes.
C'est difficile à expliquer avec des mots, je vais donc l'expliquer avec un chiffre.
Un GC mineur se produira lorsque la nouvelle mémoire est allouée et Eden est plein. Les données non référencées sont supprimées, mais les données valides sont copiées dans la zone Survivant. De plus, la zone Eden sera complètement vide.
De plus, si Eden redevient plein dans cet état, un GC mineur se produira à nouveau et le résultat sera comme indiqué dans la figure ci-dessous.
Cette fois, je suis entré dans Survivor 2 après GC. Dans la zone Survivor, les données seront copiées dans l'un ou l'autre espace libre, et 1 et 2 seront déplacés d'avant en arrière. De plus, comme Eden, les données qui ne sont pas référencées dans la zone Suvivor seront supprimées.
Vient ensuite la promotion à l'ancien. Chaque fois qu'un GC se produit, les données de Young sont enregistrées le nombre de fois, et lorsqu'elles dépassent un certain nombre de fois, elles passent à Old.
En répétant GC plusieurs fois de cette manière, le passage de Jeune à Vieux se produit. Ce nombre peut être spécifié en option, afin que vous puissiez contrôler la fréquence à laquelle vous passez à Old.
-XX:MaxTenuringThreshold=N
FullGC Je comprends comment les données sont transférées de Young à Old, mais avec cela seul, la capacité de Old continuera d'augmenter, et la capacité sera limitée quelque part. C'est là qu'intervient FullGC. FullGC se produit lorsque l'allocation à Old échoue et nettoie la mémoire, y compris Old et Young.
Cela libère de l'espace qui n'est plus nécessaire dans l'ancienne zone et vous permet de copier les données qui se trouvaient dans la zone Survivor.
Comme pour le GC mineur, l'application s'arrêtera pendant le GC complet. De plus, comme le temps d'arrêt est long car Old est inclus, il semble important de supprimer l'occurrence en utilisant autant que possible la mémoire dans la zone Young.
Résumé
-Minor GC se produit lorsque la zone Eden est pleine
Il s'est avéré que c'était un cycle.
J'ai essayé d'organiser le mécanisme de base de GC en Java. J'étais déconcerté par le fait qu'il existe différents types de JVM et ce que j'ai dit dans chaque article est légèrement différent, mais j'ai l'intention de les résumer en me basant sur les sources d'Oracle et d'Open JDK. Maintenant que l'algorithme GC sélectionné par défaut est G1GC, je vais le résumer ensuite. Tout d'abord, j'ai présenté le mécanisme de libération de la mémoire sous-jacent.
Open JDK Document Oracle Document JAVAMAGAZINE / Open JDK et nouveau garbage collector Algorithme et réglage du GC JVM
Recommended Posts