J'ai découvert par hasard la définition de "Cochon dans le Python (Remarque : c'est un peu comme le serpent gourmand et insuffisant avalant l'éléphant)" en chinois en regardant le glossaire de gestion de la mémoire, j'ai donc trouvé cet article . En apparence, ce terme fait référence au GC qui fait constamment la promotion de gros objets d’une génération à l’autre. C'est comme si un python avalait sa proie en entier, de sorte qu'elle ne pouvait pas bouger pendant qu'elle digère.
Pendant les 24 heures suivantes, mon esprit était rempli d'images de ce python suffocant dont je ne parvenais pas à me débarrasser. Comme le disent les psychiatres, la meilleure façon de soulager la peur est d’en parler. D'où cet article. Mais la prochaine histoire dont nous voulons parler n'est pas celle de Python, mais du réglage GC. Je le jure devant Dieu.
Tout le monde sait que les pauses du GC peuvent facilement provoquer des goulots d'étranglement dans les performances. Les JVM modernes sont livrées avec des garbage collectors avancés lors de leur sortie, mais d'après mon expérience, il est extrêmement difficile de trouver la configuration optimale pour une application donnée. Le réglage manuel a peut-être encore une lueur d'espoir, mais vous devez comprendre les mécanismes exacts de l'algorithme GC. À cet égard, cet article vous sera utile. Ci-dessous, j'utiliserai un exemple pour expliquer comment un petit changement dans la configuration de la JVM affecte le débit de votre application.
Exemple
L'application que nous utilisons pour démontrer l'impact du GC sur le débit n'est qu'un simple programme. Il contient deux fils :
PigEater – Il imitera le processus d'un python géant mangeant un gros cochon gras. Le code fait cela en ajoutant 32 Mo d'octets à java.util.List et en dormant 100 ms après chaque hirondelle.
PigDigester – Il simule le processus de digestion asynchrone. Le code qui implémente la digestion définit simplement la liste des porcs comme vide. Puisqu'il s'agit d'un processus fatiguant, ce thread sera mis en veille pendant 2 000 ms à chaque fois après avoir effacé la référence.
Les deux threads fonctionneront dans une boucle while, mangeant et digérant jusqu'à ce que le serpent soit plein. Cela nécessiterait de manger environ 5 000 porcs.
package eu.plumbr.demo; public class PigInThePython { static volatile List pigs = new ArrayList(); static volatile int pigsEaten = 0; static final int ENOUGH_PIGS = 5000; public static void main(String[] args) throws InterruptedException { new PigEater().start(); new PigDigester().start(); } static class PigEater extends Thread { @Override public void run() { while (true) { pigs.add(new byte[32 * 1024 * 1024]); //32MB per pig if (pigsEaten > ENOUGH_PIGS) return; takeANap(100); } } } static class PigDigester extends Thread { @Override public void run() { long start = System.currentTimeMillis(); while (true) { takeANap(2000); pigsEaten+=pigs.size(); pigs = new ArrayList(); if (pigsEaten > ENOUGH_PIGS) { System.out.format("Digested %d pigs in %d ms.%n",pigsEaten, System.currentTimeMillis()-start); return; } } } } static void takeANap(int ms) { try { Thread.sleep(ms); } catch (Exception e) { e.printStackTrace(); } } }
Nous définissons désormais le débit de ce système comme « le nombre de porcs pouvant être digérés par seconde ». En considérant qu'un cochon est introduit dans ce python toutes les 100 ms, nous pouvons voir que le débit maximum théorique de ce système peut atteindre 10 cochons/seconde.
Exemple de configuration GC
Jetons un coup d'œil aux performances de l'utilisation de deux systèmes de configuration différents. Quelle que soit la configuration, l'application fonctionne sur un Mac double cœur avec 8 Go de RAM (OS X10.9.3).
La première configuration :
Heap 1,4G (-Xms4g -Xmx4g)
2. Utilisez CMS pour nettoyer l'ancienne génération (-XX : UseConcMarkSweepGC) et utilisez le collecteur parallèle pour nettoyer la nouvelle génération Generation (-XX : UseParNewGC)
3. Allouez 12,5% du tas (-Xmn512m) à la nouvelle génération et limitez les tailles de la zone Eden et de la zone Survivor pour qu'elles soient identiques. .
La deuxième configuration est légèrement différente :
Tas 1,2G (-Xms2g -Xms2g)
2 La nouvelle génération et l'ancienne génération utilisent Parellel GC (-XX : UseParallelGC)
. 3. Allouez 75 % du tas à la nouvelle génération (-Xmn 1536m)
4. Il est maintenant temps de parier sur quelle configuration fonctionnera le mieux (c'est-à-dire combien de porcs peuvent être mangés par seconde, rappelez-vous Bar. ) ? Ceux qui ont mis leurs jetons sur la première configuration, vous serez déçus. Les résultats sont tout le contraire :
1. La première configuration (grand tas, grande ancienne génération, CMS GC) peut manger 8,2 porcs par seconde
2. La deuxième configuration (petit tas, grand Le nouveau génération, Parellel GC) peut manger 9,2 porcs par seconde
Regardons maintenant ce résultat objectivement. Les ressources allouées sont 2 fois moindres mais le débit est augmenté de 12%. Cela est contraire au bon sens et il est donc nécessaire d’analyser plus en profondeur ce qui se passe.
Analyser les résultats du GC
La raison n'est en fait pas compliquée. Il vous suffit d'examiner attentivement ce que fait le GC lors de l'exécution du test pour trouver la réponse. C'est ici que vous choisissez l'outil que vous souhaitez utiliser. Avec l'aide de jstat, j'ai découvert le secret qui se cache derrière. La commande est probablement la suivante :
jstat -gc -t -h20 PID 1s
En analysant les données, j'ai remarqué que la configuration 1 a connu 1129 cycles GC (YGCT_FGCT) et a dépensé un total de 63,723 secondes :
Timestamp S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 594.0 174720.0 174720.0 163844.1 0.0 174848.0 131074.1 3670016.0 2621693.5 21248.0 2580.9 1006 63.182 116 0.236 63.419 595.0 174720.0 174720.0 163842.1 0.0 174848.0 65538.0 3670016.0 3047677.9 21248.0 2580.9 1008 63.310 117 0.236 63.546 596.1 174720.0 174720.0 98308.0 163842.1 174848.0 163844.2 3670016.0 491772.9 21248.0 2580.9 1010 63.354 118 0.240 63.595 597.0 174720.0 174720.0 0.0 163840.1 174848.0 131074.1 3670016.0 688380.1 21248.0 2580.9 1011 63.482 118 0.240 63.723
La deuxième configuration a fait une pause au total 168 fois (YGCT FGCT) et n'a pris que 11,409 secondes.
Timestamp S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 539.3 164352.0 164352.0 0.0 0.0 1211904.0 98306.0 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409 540.3 164352.0 164352.0 0.0 0.0 1211904.0 425986.2 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409 541.4 164352.0 164352.0 0.0 0.0 1211904.0 720900.4 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409 542.3 164352.0 164352.0 0.0 0.0 1211904.0 1015812.6 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409
Considérant que la charge de travail dans les deux cas est égale, donc - dans cette expérience de consommation de porcs, lorsque le GC ne trouve pas d'objets à longue durée de vie, il peut nettoyer les déchets plus rapidement. Avec la première configuration, la fréquence de fonctionnement du GC sera d'environ 6 à 7 fois et le temps de pause total sera de 5 à 6 fois.
Raconter cette histoire a deux objectifs. Avant tout, je voulais sortir de mon esprit ce python convulsif. Un autre avantage plus évident est que le réglage GC est une expérience très habile et nécessite que vous ayez une compréhension approfondie des concepts sous-jacents. Bien que celle utilisée dans cet article ne soit qu’une application très courante, les différents résultats de la sélection auront également un impact important sur votre planification de débit et de capacité. Dans les applications réelles, la différence sera encore plus grande. C'est donc à vous de décider si vous pouvez maîtriser ces concepts, ou simplement vous concentrer sur votre travail quotidien et laisser Plumbr déterminer la configuration GC la plus adaptée à vos besoins.
Pour plus d'articles liés au test de l'impact du garbage collector GC sur le débit en Java, veuillez faire attention au site Web PHP chinois !