Maison > Article > développement back-end > Introduction détaillée au mécanisme de garbage collection de Java et C#
(1) Hypothèses de base du garbage collector
(1) Les objets qui ont récemment reçu de l'espace mémoire sont les plus susceptibles d'avoir besoin de être libéré. Avant qu'une méthode ne soit exécutée, il est généralement nécessaire d'allouer de l'espace mémoire aux objets utilisés par la méthode. La recherche dans la collection d'objets récemment alloués peut aider à libérer autant d'espace mémoire libre que possible avec le moins de travail possible.
(2) L'objet ayant la durée de vie la plus longue est le moins susceptible de devoir être libéré. Il est peu probable que les objets qui existent encore après plusieurs cycles de récupération de place soient des objets temporaires pouvant être libérés lors du prochain cycle de récupération de place. La recherche de ces blocs de mémoire nécessite souvent beaucoup de travail, mais seule une petite partie de l'espace mémoire peut être récupérée. libéré. .
(3) Les objets auxquels on alloue de la mémoire en même temps sont généralement utilisés en même temps. La connexion d’emplacements de stockage d’objets qui s’attribuent de la mémoire en même temps peut contribuer à améliorer les performances du cache.
(2) Plusieurs mécanismes de collecte des déchets
(1) Collecteur de marquage
Ce collecteur parcourt d'abord le graphique d'objets et marque les objets accessibles, puis analyse la pile à la recherche d'objets non marqués et libère leur mémoire. Ce type de collecteur utilise généralement un seul thread pour fonctionner et arrête les autres opérations.
(2) Mark-Compact Collector
Parfois, il est également appelé Mark-Sweep-Compact Collector, qui est le même que Mark- Sweep Collector. Même phase de marquage. Dans un deuxième temps, l'objet marqué est copié dans une nouvelle zone de la pile afin de compresser la pile. Ce collecteur arrête également d'autres opérations.
(3) Collecteur de copie
Ce collecteur divise la pile en deux domaines, souvent appelés demi-espace. Seule la moitié de l'espace est utilisée à chaque fois, et les nouveaux objets générés par le jvm sont placés dans l'autre moitié de l'espace. Lorsque gc s'exécute, il copie les objets accessibles dans l'autre moitié de l'espace, compressant ainsi la pile. Cette méthode convient aux objets à courte durée de vie. La copie continue d'objets à longue durée de vie entraînera une efficacité réduite.
(4) Collecteur incrémentiel
Le collecteur incrémentiel divise la pile en plusieurs domaines et ne collecte les déchets que d'un domaine à la fois. Cela entraîne une perturbation mineure de l’application.
(5) Collecteur générationnel
Ce collecteur divise la pile en deux domaines ou plus pour stocker différents objets de durée de vie. Les nouveaux objets générés par jvm sont généralement placés dans l'un des champs. Au fil du temps, les objets survivants acquerront une durée de vie utile et seront transférés vers un domaine à durée de vie plus longue. Les collectionneurs générationnels utilisent différents algorithmes pour différents domaines afin d'optimiser les performances.
(6) Collecteur simultané
Le collecteur simultané s'exécute en même temps que l'application. Ces collecteurs doivent généralement arrêter d'autres opérations à un moment donné (comme le compactage) pour terminer une tâche spécifique, mais comme d'autres applications peuvent effectuer d'autres opérations en arrière-plan, le temps réel nécessaire pour interrompre d'autres traitements est considérablement réduit.
(7) Collecteur parallèle
Les collecteurs parallèles utilisent un algorithme traditionnel et utilisent plusieurs threads pour effectuer leur travail en parallèle. L'utilisation de la technologie multithread sur des machines multi-CPU peut améliorer considérablement l'évolutivité des applications Java.
(3) Mécanisme de récupération de place du .NET Framework
.NET Framework contient un tas géré, tout. le langage l’utilise lors de l’allocation d’objets de type référence. Les objets légers tels que les types valeur sont toujours alloués sur la pile, mais toutes les instances de classe et tous les tableaux sont générés dans un pool de mémoire, qui est le tas géré.
Le garbage collector dans le framework .NET est appelé garbage collector générationnel (Generational Garbage Collector), ce qui signifie que les objets alloués sont divisés en trois catégories, ou Pour "génération". Elles sont respectivement 0, 1 et 2. Les tailles initiales des tas gérés correspondant aux générations 0, 1 et 2 sont respectivement de 256 Ko, 2 Mo et 10 Mo. Le garbage collector modifiera la taille du tas géré s’il estime que la modification de la taille améliorera les performances. Par exemple, lorsqu'une application initialise de nombreux petits objets et que ces objets seront recyclés rapidement, le garbage collector augmentera le tas géré dans la génération 0 à 128 Ko et augmentera la fréquence de recyclage. Si la situation est inversée et que le garbage collector constate qu'il ne peut pas récupérer beaucoup d'espace dans le tas géré dans la génération 0, il augmentera la taille du tas géré. Tous les niveaux du tas géré sont vides jusqu'à ce que l'application soit initialisée. Lorsque les objets sont initialisés, ils sont placés dans le tas géré dans la génération 0 dans l'ordre dans lequel ils ont été initialisés.
Les objets qui ont récemment reçu de l'espace mémoire sont placés dans la génération 0. Parce que la génération 0 est petite, suffisamment petite pour tenir dans le cache de deuxième niveau (L2) du processeur, la génération 0 peut nous fournir un accès rapide aux objets en son sein. Après un cycle de garbage collection, les objets encore dans la génération 0 sont déplacés vers la génération 1. Après un autre cycle de garbage collection, les objets encore dans la génération 1 sont déplacés vers la génération 2. . La génération 2 contient des objets de longue durée qui ont fait l'objet d'au moins deux cycles de collecte.
Lorsqu'un programme C# alloue de la mémoire pour un objet, le tas géré peut restituer presque immédiatement la mémoire requise pour le nouvel objet. C'est la raison pour laquelle le tas géré peut avoir des performances d'allocation de mémoire si efficaces. C'est parce que le tas géré Une structure de données relativement simple. Le tas géré est similaire à un simple tableau d’octets, avec un pointeur vers le premier espace mémoire disponible.
Lorsqu'un bloc est demandé par un objet, la valeur du pointeur ci-dessus sera renvoyée à la fonction appelante, et le pointeur sera réajusté pour pointer vers le prochain espace mémoire disponible. L'allocation d'un bloc de mémoire gérée n'est que légèrement plus compliquée que l'incrémentation de la valeur d'un pointeur. C'est également l'une des optimisations de performances du tas géré. Dans une application qui ne nécessite pas beaucoup de garbage collection, le tas géré fonctionnera mieux que le tas traditionnel.
En raison de cette méthode d'allocation de mémoire linéaire, les objets alloués simultanément dans les applications C# sont généralement alloués les uns à côté des autres sur le tas géré. Cette disposition est complètement différente de l'allocation de mémoire tas traditionnelle, qui est basée sur la taille des blocs de mémoire. Par exemple, deux objets alloués en même temps peuvent être éloignés l'un de l'autre sur le tas, ce qui réduit les performances du cache. Par conséquent, même si l'allocation de mémoire est rapide, dans certains programmes plus importants, la mémoire disponible dans la génération 0 est susceptible d'être complètement consommée. N'oubliez pas que la génération 0 est suffisamment petite pour tenir dans le tampon L2 et que la mémoire inutilisée n'est pas automatiquement libérée. Lorsqu'il n'y a pas de mémoire valide pouvant être allouée à la génération 0, un cycle de garbage collection sera déclenché dans la génération 0. Dans ce cycle de garbage collection, tous les objets qui ne sont plus référencés seront supprimés et les objets actuellement utilisés seront supprimé. Déplacé vers la génération 1. Le garbage collection de génération 0 est le type de collecte le plus courant et il est très rapide. Lorsque la récupération de mémoire de la 0ème génération ne peut pas effectivement demander suffisamment de mémoire, la récupération de mémoire de la 1ère génération est démarrée. Le garbage collection de génération 2 doit être utilisé en dernier recours si et seulement si le garbage collection de génération 1 et de génération 0 ne peut pas fournir suffisamment de mémoire. S'il n'y a toujours pas de mémoire disponible après le garbage collection à chaque génération, une OutOfMemeryException sera levée.
(4) Mécanisme de récupération de place Java
Comment la mémoire est-elle placée lors de l'exécution d'un programme Java ? Six endroits sont mentionnés dans le livre "Java Programming Thoughts":
(1) Register (Register)
(2) Stack ( Stack )
(3) Heap : utilisé pour placer tous les objets Java
(4) Stockage statique ) : utilisé pour stocker les données qui existent " pendant l'exécution du programme". Modifié avec statci.
(5) Stockage constant
(6) Espace de stockage non RAM : je le comprends comme une zone de stockage sur disque, c'est-à-dire zone non-mémoire.
Sun HotSpot 1.4.1 utilise un collecteur générationnel, qui divise le tas en trois domaines principaux : nouveau domaine, ancien domaine et domaine permanent. Tous les nouveaux objets générés par Jvm sont placés dans le nouveau domaine. Une fois qu'un objet a subi un certain nombre de cycles de garbage collection, il acquiert sa durée de vie et entre dans l'ancien domaine. Dans le domaine permanent, la jvm stocke les objets de classe et de méthode. À des fins de configuration, le domaine persistant est un domaine distinct et n'est pas considéré comme faisant partie du tas. De ce point de vue, la JVM utilisant la technologie du moteur HotSpot devrait adopter un mécanisme de garbage collection similaire au framework .NET - méthode de garbage collection générationnelle.
Voici comment contrôler la taille de ces domaines. Vous pouvez utiliser -Xms et -Xmx pour contrôler la taille d'origine ou la taille maximale de l'ensemble du tas.
La commande suivante définit la taille initiale à 128M :
java –Xms128m
- Xmx256m Pour contrôler la taille du nouveau domaine, vous pouvez utiliser -XX:NewRatio pour définir la proportion du nouveau domaine dans le tas.
La commande suivante définit le tas entier à 128 m et le rapport du nouveau domaine à 3, c'est-à-dire que le rapport du nouveau domaine à l'ancien domaine est de 1:3, et le nouveau le domaine fait 1/4 du tas ou 32M :
java –Xms128m –Xmx128m
–XX:NewRatio =3 Vous pouvez utiliser -XX:NewSize et -XX:MaxNewsize pour définir la valeur initiale et la valeur maximale du nouveau domaine.
La commande suivante définit les valeurs initiales et maximales du nouveau domaine à 64m :
java –Xms256m –Xmx256m –Xmn64m
La taille par défaut du domaine permanent est de 4m. Lors de l'exécution d'un programme, la JVM ajuste la taille du domaine persistant en fonction des besoins. Chaque fois que vous effectuez un ajustement, la JVM effectuera un garbage collection complet sur le tas.
Utilisez l'indicateur -XX:MaxPerSize pour augmenter la taille du domaine permanent. Lorsqu'une application WebLogic Server charge davantage de classes, il est souvent nécessaire d'augmenter la taille maximale du domaine permanent. Lorsque la JVM charge une classe, les objets du domaine permanent augmentent considérablement, ce qui oblige la JVM à ajuster continuellement la taille du domaine permanent. Pour éviter les ajustements, utilisez l'indicateur -XX:PerSize pour définir une valeur initiale.
Ensuite, définissez la valeur initiale du domaine permanent à 32m et la valeur maximale à 64m.
java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m
Par défaut, HotSpot A réplication collector est utilisé dans le domaine. Le domaine est généralement divisé en trois parties. La première partie est Eden, qui sert à générer de nouveaux objets. Les deux autres parties sont appelées espaces de secours. Lorsque Eden est plein, le collecteur arrête l'application et copie tous les objets accessibles vers le courant depuis l'espace de secours. Une fois le courant depuis l'espace de secours est plein, le collecteur copie les objets accessibles vers le courant depuis. espace de sauvetage. L'espace de sauvetage. Depuis et vers les espaces de secours, échangez les rôles. Les objets qui restent vivants seront répliqués dans l'espace de récupération jusqu'à leur expiration et seront transférés vers l'ancien domaine. Utilisez -XX:SurvivorRatio pour contrôler la taille du nouveau sous-espace de domaine.
Comme NewRation, SurvivorRation spécifie le rapport d'un certain domaine de sauvetage à l'espace Eden. Par exemple, la commande suivante définit le nouveau domaine sur 64 m, Eden occupe 32 m et chaque domaine de secours occupe 16 m :
java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2
Comme mentionné précédemment, HotSpot utilise par défaut un collecteur de copies pour les nouveaux domaines et un collecteur mark-sweep-compact pour les anciens domaines. Utiliser un collecteur de copie dans un nouveau domaine est tout à fait logique car la plupart des objets générés par une application sont de courte durée. Idéalement, tous les objets de transition seront collectés lorsqu'ils seront déplacés hors de l'espace Eden. Si cela est possible et que les objets déplacés hors de l'espace Eden ont une longue durée de vie, alors en théorie, ils peuvent être déplacés immédiatement vers l'ancien espace pour éviter des copies répétées dans l'espace de sauvetage. Cependant, les applications ne peuvent pas correspondre à cet état idéal car elles comportent une faible proportion d’objets à durée de vie moyenne à longue. Il est préférable de conserver ces objets à durée de vie moyenne à longue dans le nouveau domaine, car il est moins coûteux de copier de petites parties des objets que de compresser l'ancien domaine. Afin de contrôler la copie des objets dans le nouveau domaine, vous pouvez utiliser -XX:TargetSurvivorRatio pour contrôler le ratio de l'espace de secours (cette valeur permet de définir le taux d'utilisation de l'espace de secours. Par exemple, le bit de l'espace de secours est 1M, et la valeur 50 signifie que 500K sont disponibles). La valeur est un pourcentage et la valeur par défaut est 50. Lorsque des piles plus grandes utilisent un ratio de survie inférieur, cette valeur doit être augmentée entre 80 et 90 pour mieux utiliser l'espace de sauvetage. Utilisez -XX:maxtenuring seuil pour contrôler la limite supérieure.
Pour garantir que toutes les réplications se produisent et que vous souhaitez que les objets soient étendus d'Eden à l'ancien domaine, définissez le seuil MaxTenuring sur 0. Une fois le réglage terminé, l'espace de secours n'est en fait plus utilisé, le SurvivorRatio doit donc être défini sur la valeur maximale pour maximiser l'espace Eden. Les paramètres sont les suivants :
java. ... -XX:MaxTenuringThreshold= 0 –XX:SurvivorRatio=50000 …
Postscript : Comme mentionné dans "Les 10 ans de la machine virtuelle Java", " Les cinq dernières années, c'est-à-dire que (JVM) continue d'optimiser pendant cinq ans. Il existe plusieurs façons de poursuivre l'optimisation. La première consiste à étudier de nouveaux algorithmes d'échantillonnage, car l'échantillonnage est lié à différentes stratégies d'optimisation et aura un impact relativement important sur. performances globales.Étudier les méthodes d'optimisation approfondies.Le troisième est d'étudier les algorithmes de collecte des ordures qui provoqueront une courte pause dans le programme, ce qui entraînera une expérience utilisateur négative.Par conséquent, une variété d'algorithmes ont émergé pour améliorer l'efficacité de la collecte des ordures. et réduire les retards, tels que la collecte progressive, l'algorithme de train, etc. "Améliorer la vitesse d'exécution et l'efficacité du langage a toujours été l'objectif poursuivi par les concepteurs et les développeurs, de sorte que les algorithmes de collecte des ordures se développeront également avec le temps. Je ne pense pas qu'un intervieweur oserait vous demander de parler du mécanisme de récupération de place de C# ou de Java (du moins, je ne l'ai pas encore rencontré). Une fois la discussion approfondie, de nombreux problèmes suffisent pour rédiger un article. long livre. Mais c'est vraiment une belle chose d'enquêter en profondeur et de retracer l'origine. Confucius a grimpé jusqu'à la montagne orientale et est devenu un petit Lu, puis a grimpé jusqu'à la montagne Taishan et est devenu un petit monde.
Ce qui précède est une introduction détaillée du mécanisme de récupération de place de Java et C#. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !