Machine virtuelle Java - GC générationnels

La plupart des JVM divisent le tas en trois générations - the young generation (YG), the old generation (OG) and permanent generation (also called tenured generation). Quelles sont les raisons d'une telle réflexion?

Des études empiriques ont montré que la plupart des objets créés ont une durée de vie très courte -

La source

https://www.oracle.com

Comme vous pouvez le voir, à mesure que de plus en plus d'objets sont alloués avec le temps, le nombre d'octets survivants diminue (en général). Les objets Java ont un taux de mortalité élevé.

Nous examinerons un exemple simple. La classe String en Java est immuable. Cela signifie que chaque fois que vous devez modifier le contenu d'un objet String, vous devez créer un nouvel objet. Supposons que vous apportiez des modifications à la chaîne 1000 fois dans une boucle comme indiqué dans le code ci-dessous -

String str = “G11 GC”;

for(int i = 0 ; i < 1000; i++) {
   str = str + String.valueOf(i);
}

Dans chaque boucle, nous créons un nouvel objet chaîne, et la chaîne créée lors de l'itération précédente devient inutile (c'est-à-dire qu'elle n'est référencée par aucune référence). La durée de vie de cet objet n'était qu'une itération - ils seront collectés par le GC en un rien de temps. Ces objets de courte durée sont conservés dans la zone des jeunes générations du tas. Le processus de collecte des objets de la jeune génération s'appelle le ramassage des ordures mineures et provoque toujours une pause «stopthe-world».

Au fur et à mesure que la jeune génération se remplit, le GC fait un petit garbage collection. Les objets morts sont supprimés et les objets vivants sont déplacés vers l'ancienne génération. Les threads d'application s'arrêtent pendant ce processus.

Ici, nous pouvons voir les avantages qu'offre un tel design de génération. La jeune génération n'est qu'une petite partie du tas et se remplit rapidement. Mais le traitement prend beaucoup moins de temps que le temps nécessaire pour traiter l'ensemble du tas. Ainsi, les pauses «stop-theworld» dans ce cas sont beaucoup plus courtes, bien que plus fréquentes. Nous devrions toujours viser des pauses plus courtes sur des pauses plus longues, même si elles peuvent être plus fréquentes. Nous en discuterons en détail dans les sections suivantes de ce didacticiel.

La jeune génération est divisée en deux espaces - eden and survivor space. Les objets qui ont survécu pendant la collection d'Eden sont déplacés vers l'espace de survivant, et ceux qui survivent à l'espace de survivant sont déplacés vers l'ancienne génération. La jeune génération est compactée pendant sa récolte.

Lorsque les objets sont déplacés vers l'ancienne génération, ils finissent par se remplir et doivent être collectés et compactés. Différents algorithmes adoptent différentes approches à cet égard. Certains d'entre eux arrêtent les threads d'application (ce qui conduit à une longue pause `` stop-the-world '' puisque l'ancienne génération est assez grande par rapport à la jeune génération), tandis que certains le font simultanément pendant que les threads d'application continuent de fonctionner. Ce processus est appelé GC complet. Deux de ces collectionneurs sontCMS and G1.

Analysons maintenant ces algorithmes en détail.

GC série

c'est le GC par défaut sur les machines de classe client (machines monoprocesseur ou JVM 32b, Windows). En règle générale, les GC sont fortement multithread, mais pas le GC série. Il a un seul thread pour traiter le tas et il arrêtera les threads d'application chaque fois qu'il effectue un GC mineur ou un GC majeur. Nous pouvons commander à la JVM d'utiliser ce GC en spécifiant le drapeau:-XX:+UseSerialGC. Si nous voulons qu'il utilise un algorithme différent, spécifiez le nom de l'algorithme. A noter que l'ancienne génération est entièrement compactée lors d'un GC majeur.

GC de débit

Ce GC est par défaut sur les JVM 64b et les machines multi-processeurs. Contrairement au GC série, il utilise plusieurs threads pour traiter la jeune et l'ancienne génération. Pour cette raison, le GC est également appelé leparallel collector. Nous pouvons commander à notre JVM d'utiliser ce collecteur en utilisant l'indicateur:-XX:+UseParallelOldGC ou -XX:+UseParallelGC(à partir de JDK 8). Les threads d'application sont arrêtés pendant qu'il effectue un garbage collection majeur ou mineur. Comme le collectionneur en série, il compacte entièrement la jeune génération lors d'un grand GC.

Le GC de débit collecte le YG et l'OG. Lorsque l'eden est rempli, le collecteur en éjecte des objets vivants dans l'OG ou dans l'un des espaces survivants (SS0 et SS1 dans le diagramme ci-dessous). Les objets morts sont jetés pour libérer l'espace qu'ils occupaient.

Avant GC de YG

Après GC de YG

Pendant un CPG complet, le collecteur de débit vide l'intégralité de YG, SS0 et SS1. Après l'opération, l'OG ne contient que des objets vivants. Nous devons noter que les deux collecteurs ci-dessus arrêtent les threads d'application pendant le traitement du tas. Cela signifie de longues pauses «arrêt du monde» pendant un GC majeur. Les deux algorithmes suivants visent à les éliminer, au prix de plus de ressources matérielles -

Collecteur CMS

Il signifie «balayage de marque simultané». Sa fonction est d'utiliser des threads d'arrière-plan pour parcourir périodiquement l'ancienne génération et se débarrasser des objets morts. Mais pendant un GC mineur, les threads d'application sont arrêtés. Cependant, les pauses sont assez petites. Cela fait du CMS un collecteur à faible pause.

Ce collecteur a besoin de temps CPU supplémentaire pour parcourir le tas lors de l'exécution des threads d'application. De plus, les threads d'arrière-plan collectent simplement le tas et n'effectuent aucun compactage. Ils peuvent conduire à la fragmentation du tas. Au fur et à mesure que cela continue, après un certain temps, le CMS arrêtera tous les threads d'application et compactera le tas en utilisant un seul thread. Utilisez les arguments JVM suivants pour indiquer à la JVM d'utiliser le collecteur CMS -

“XX:+UseConcMarkSweepGC -XX:+UseParNewGC” comme arguments JVM pour lui dire d'utiliser le collecteur CMS.

Avant GC

Après GC

Notez que la collecte est effectuée simultanément.

GC G1

Cet algorithme fonctionne en divisant le tas en un certain nombre de régions. Comme le collecteur CMS, il arrête les threads d'application tout en effectuant un GC mineur et utilise des threads d'arrière-plan pour traiter l'ancienne génération tout en maintenant les threads d'application. Puisqu'il a divisé l'ancienne génération en régions, il continue de les compacter tout en déplaçant des objets d'une région à une autre. Par conséquent, la fragmentation est minimale. Vous pouvez utiliser le drapeau:XX:+UseG1GCpour dire à votre JVM d'utiliser cet algorithme. Comme CMS, il a également besoin de plus de temps CPU pour traiter le tas et exécuter simultanément les threads d'application.

Cet algorithme a été conçu pour traiter des tas plus volumineux (> 4G), qui sont divisés en plusieurs régions différentes. Certaines de ces régions comprennent la jeune génération et les autres comprennent les personnes âgées. Le YG est effacé en utilisant traditionnellement - tous les threads d'application sont arrêtés et tous les objets qui sont encore vivants pour l'ancienne génération ou l'espace de survie.

Notez que tous les algorithmes GC ont divisé le tas en YG et OG et utilisent un STWP pour effacer le YG. Ce processus est généralement très rapide.