Réglage des garbage collectors (GC) sur JDK1 Réglage des garbage collectors (GC) sur JDK1.6 Exemple de la plateforme d’échanges OSB/MOM A. Dewavrin - 3 Juin 2013 CTR
Historique des versions Date Auteur Actions Version 03/06/2013 Anselme Dewavrin Récapitulatif du travail d’amélioration des GC de la plateforme d’échange V0.1 05/06/2013 Ajouts et conclusion V0.2 06/06/2013 Simplifications et schémas V0.3 CTR
Problématique Les JVMs* se figent régulièrement, tour-à-tour, typiquement pendant des durées de quelques secondes Conséquences : Ces pauses peuvent provoquer des timeouts sur les appelants (Tunnel de commande, Compiere, historisation) Suspicion de nombreux dysfonctionnements (abandon de transactions etc.) dans le MOM et l’OSB en prod. La moitié des trous dans l’historique Flowtracker est due à ces pauses. La cause : Un code Java ne relâche jamais la mémoire. C’est un mécanisme (garbage collector) qui fait régulièrement la récupération de la mémoire abandonnée, provoquant des pauses. *par exemple sur la plateforme d’échange, décrite en annexe
Anatomie de la mémoire d’une JVM New (ou Young) Les objets sont créés en Eden Eden La zone « new » est balayée par un garbage-collector (minor) qui provoque normalement des pauses courtes… Les objets ayant survécu à un ou plusieurs passages du garbage collector de la zone « new » sont placés en Survivor, alternativement S1 et S2 S1 S2 Old (ou Tenured) …la zone « old » par un autre GC (major), le plus connu car son réglage par défaut provoque des pauses longues de la JVMs, parfois de plusieurs dizaines de secondes. On appelle cela « full garbage ». Les objets ayant survécu à de nombreux passages du garbage collector de la zone « new » sont placés en zone « old » (c’est ce qui est nommé « promotion ») Les objets et métadonnées (classes, etc.) éternels sont placés dans la zone « permanent » Perm xss xss xss Les données d’appel et retour de chaque thread sont stockées en zone « XSS »
Activité des garbage collectors 1. L’application demande régulièrement de la mémoire mais ne la libère jamais (principe de java). La mémoire est réservée en zone « new » (précisément, en Eden) 2. Quand la new est pleine, un garbage de zone « new » est déclenché. 3. Dans le cas idéal tous les objets sont désormais inutilisés et la mémoire est récupérée 4. Quand l’application est plus chargée, la new se remplit plus vite et les garbages new sont plus fréquents 5. Quand il reste des objets encore utilisés, le garbage ne peut pas récupérer leur mémoire, il les laisse en new mais les déplace dans une des zones « survivor » 6. Quand des objets ont survécu à plusieurs garbages, ils sont considérés comme durables, et sont promus en zone « Old » ce qui leur évite d’être scannés tout le temps pour rien par le garbage de zone « new ». 7. Quand la zone « old » est remplie, un garbage « full » se déclenche et récupère la mémoire des objets inutilisés. Par défaut cela provoque une pause (nommée « stop-the-world ») 8. Le niveau atteint après un garbage full donne une idée de la mémoire minimum nécessaire à l’application. Il ne reste que les objets très durables. Si trop peu de mémoire est récupérée on a un out-of-memory de type « java heap». Si la JVM passe plus de 99% de son temps à faire du garbage on a un out-of-memory de type «GC overhead limit » 2 1 4 New 5 3 6 7 Old 8
Logs de l’activité des garbage collectors Il est possible (souhaitable) de demander au garbage de logger son activité. Voici ce qu’on y voit : {Heap before GC invocations=7791 (full 39): par new generation total 917504K, used 867685K [0x0000000714400000, 0x0000000754400000, 0x0000000754400000) eden space 786432K, 100% used [0x0000000714400000, 0x0000000744400000, 0x0000000744400000) from space 131072K, 61% used [0x000000074c400000, 0x0000000751359768, 0x0000000754400000) to space 131072K, 0% used [0x0000000744400000, 0x0000000744400000, 0x000000074c400000) concurrent mark-sweep generation total 2097152K, used 1750952K [0x0000000754400000, 0x00000007d4400000, 0x00000007d4400000) concurrent-mark-sweep perm gen total 716800K, used 276326K [0x00000007d4400000, 0x0000000800000000, 0x0000000800000000) 218751.537: [GC 218751.537: [ParNew Desired survivor size 67108864 bytes, new threshold 15 (max 15) - age 1: 8197912 bytes, 8197912 total - age 2: 5748856 bytes, 13946768 total - age 3: 513544 bytes, 14460312 total - age 4: 845536 bytes, 15305848 total - age 5: 799008 bytes, 16104856 total - age 6: 514008 bytes, 16618864 total - age 7: 6038896 bytes, 22657760 total - age 8: 6732456 bytes, 29390216 total - age 9: 8004688 bytes, 37394904 total - age 10: 4099760 bytes, 41494664 total - age 11: 4469168 bytes, 45963832 total - age 12: 51968 bytes, 46015800 total - age 13: 4498888 bytes, 50514688 total - age 14: 76408 bytes, 50591096 total - age 15: 558072 bytes, 51149168 total : 867685K->61866K(917504K), 0.1913490 secs] 2618638K->1812977K(3014656K), 0.1925590 secs] [Times: user=0.55 sys=0.01, real=0.20 secs] Heap after GC invocations=7792 (full 39): par new generation total 917504K, used 61866K [0x0000000714400000, 0x0000000754400000, 0x0000000754400000) eden space 786432K, 0% used [0x0000000714400000, 0x0000000714400000, 0x0000000744400000) from space 131072K, 47% used [0x0000000744400000, 0x000000074806a930, 0x000000074c400000) to space 131072K, 0% used [0x000000074c400000, 0x000000074c400000, 0x0000000754400000) concurrent mark-sweep generation total 2097152K, used 1751111K [0x0000000754400000, 0x00000007d4400000, 0x00000007d4400000) } Le remplissage des zones survivor est indiqué en tant que « from » et « to » L’âge maximum avant de passer en zone « old » Les objets du survivor, ayant survécu à 1 ou plusieurs garbages de la zone « new », peuvent être indiqués sur demande dans le log gc (paramètre printTenuringDistribution). Ici, après 15 ils seront déplacés en zone « old ». C’est la « promotion ». La durée de la pause causée par le GC
Démographie et réglages de la zone « survivor » Eden Eden Eden Age 1 Age 2 Age 3 Age 4 Age 5 Age 6 Age 7 Age 8 Age 9 Age 10 … Age 15 Le process de maturation (tenuring) est trop court. Trop d’objets sont copiés en old alors qu’ils vont mourir peu après. Cela provoque un remplissage inutile de la old et des garbages plus fréquents, et donc plus de pauses. Paramètre : SurvivorRatio L’âge maxi en survivor est trop grand, on pourrait promouvoir plus vite les objets en old pour leur éviter d’être scannés de nombreuses fois pour rien par le garbage collector de la zone « New ». A gauche, le cas d’une charge régulière, à droite une charge par à-coups Paramètre : MaxTenuringThreshold Situation idéale : à la fin de la maturation, il reste en survivor uniquement les objets durables. Seuls ceux-là seront promus en old. Old Old Old
Réglages de la zone « new » Eden Eden Age 1 Age 2 Age 3 Age 4 Age 5 Age 6 Age 7 Age 8 Age 9 Age 10 … Age 15 Situation idéale Le Eden est trop court. En l’agrandissant un peu on y aurait laissé mourir les objets les plus éphémères, évitant une copie pour rien d’un grand nombre d’objets dans les premiers âges des survivors Paramètre : NewRatio ou NewSize Old Old
Proportions de la mémoire d’une JVM New (ou Young) NewRatio Rapport entre Old et New. Typiquement 2 Eden S1 S2 Xmx (le « heap ») Typiquement 2G SurvivorRatio Rapport entre Eden et un Survivor. Typiquement 6 car on observe qu’un objet sur 6 n’est pas éphémère Old (ou Tenured) PermSize Typiquement 0.5 à 1 G Suivant la complexité de l’application (nb de classes) Perm xss xss xss Xss Taille de la stack d’une thread. Laisser par défaut sauf si erreurs « stack overflow »
Pauses d’une application java Une application Java fait des pauses, c’est un fait. Pour les minimiser il faut éviter les promotions inutiles vers la zone « Old ». Et quand c’est impossible, on abandonnera le garbage collector de zone « Old »par défaut pour utiliser au choix CMS ou parallèle. Type de GC Old avantages Inconvénients Par défaut Efficacité CPU et RAM. Conçu pour les machines monoprocesseur. Pauses longues CMS Pauses très réduites Fragmentation de la mémoire java Parallèle Pauses réduites inefficacité CPU. La parallélisation sur 4 procs n’apporte qu’une réduction par 2 des pauses Choisi
Détection des pauses d’une application java Quand on suspecte une application de faire des pauses longues dues à sa gestion de mémoire (temps de réponses de temps en temps beaucoup plus grands, attente hors charge avec un cpu à 100%), on activera le log de l’activité du garbage collector avec les paramètres de lancement suivants : -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:chemin/gc.log Ensuite après de l’activité, on pourra rechercher dans le fichier le mot « real » avec par exemple la commande : grep real gc.log Pauses du GC de zone « New » : : 864576K->92106K(917504K), 0.2122710 secs] 2330426K->1560757K(3014656K), 0.2134260 secs] [Times: user=0.62 sys=0.00, real=0.22 secs] Pauses du GC de zone « Old » (full GC): 15423.664: [Full GC [PSYoungGen: 10981K->0K(950016K)] [PSOldGen: 2068450K->967902K(2097152K)] 2079431K->967902K(3047168K) [PSPermGen: 266201K->266201K(716800K)], 4.5992780 secs] [Times: user=4.59 sys=0.01, real=4.60 secs]
Diminution des pauses d’une JVM Zone « new » : La parallélisation est possible sur les machines multicoeurs. Il y a toujours des pauses, mais plus courtes. Réglage par défaut Réglage parallèle. Paramètres : « UseParNewGC » « ParallelGCThreads =4 » New 1 2 pause 0.4s pause 0.1s Zone « old » : L’utilisation d’un algorithme concurrent (qui se déroule en grande partie en même temps que l’application), permet de limiter les « stop-the-world » 3. Réglage par défaut 4. Réglage CMS. Paramètres : « UseConcMarkSweepGC » 4 3 Old pause 0.5s (remark) pause 1s (initial mark) pause 8s
Mesure de la réduction des pauses Après changement des réglages, on relancera les mesures. Zone « New » : grep real gc.log : 817350K->90921K(917504K), 0.1499620 secs] 817350K->90921K(3014656K), 0.1500920 secs] [Times: user=0.51 sys=0.05, real=0.15 secs] Zone « Old » Comme on a mis en place le CMS, il faut uniquement prendre en compte les phases « initial mark » et « remark », les seules qui gèlent la JVM. On voit que les pauses ont été raccourcies grep remark\|initial gc.log 305062.563: [GC [1 CMS-initial-mark: 1791541K(2097152K)] 1929285K(3014656K), 0.3005340 secs] [Times: user=0.29 sys=0.01, real=0.30 secs] 302772.317: [GC[YG occupancy: 519538 K (917504 K)]302772.317: [Rescan (parallel) , 0.4999590 secs]302772.817: [weak refs processing, 0.0030560 secs] [1 CMS-remark: 1982935K(2097152K)] 2502474K(3014656K), 0.5036940 secs] [Times: user=1.34 sys=0.01, real=0.51 secs]
Causes d’échec du CMS Deux cas peuvent provoquer un échec du CMS, ce qui n’est pas grave mais conduit à un GC classique, et donc une pause longue. Type d’échec Cause schéma détection Comment éviter Fragmentation de la zone « Old » Promotion impossible de la New vers la Old. En effet le CMS ne défragmente pas la mémoire, formant un gruyère avec des trous de plus en plus petits. Les gros objets ne peuvent plus être promus grep fail gc.log 214371.138: [GC 214371.138: [ParNew (promotion failed) En dev : on évitera de manipuler de gros objets longtemps. En prod : on mettra plus de Old Urgence Une accélération de l’activité rend improbable de finir à temps le garbage full en mode concurrent. Le CMS compare de temps qu’il lui faudra au temps qu’il reste avant que la zone « Old » ne soit pleine. Il peut alors décider de faire un GC classique dans le but de geler la JVM, arrêtant ainsi le remplissage. (concurrent mode failure): 1942604K->1301745K(2097152K), 15.0272900 secs] 2835325K->1301745K(3014656K), [CMS Perm : 281374K->275121K(716800K)], 16.8480540 secs] [Times: user=14.61 sys=0.11, real=16.85 secs] En dev : on évitera les gros objets En prod On évitera les promotions inutiles (réglage eden et survivors) on déclenchera plus tôt le CMS au moyen du paramètre CMSInitiatingOccupancyFraction (80% à 92% par défaut). ! ?
Dimensionnement RAM des machines Méthode théorique Il faut calculer : heap + perm + nb_threads * profondeur d’appels + empreinte fichiers ouverts Et ajouter l’OS, les autres applications (easytrust, omnivision, tivoli, etc.), des caches et buffers disque. Méthode itérative: 1. On choisi une des configurations typiques : 2. On ajuste la mémoire heap des JVMS en fonction du niveau bas du Old (après un GC full). On règle le « Old » à deux ou trois fois le niveau bas. 3. On surveille le swapping au moyen des colonnes « si » et « so » de vmstat (et non pas sur la commande free comme le fait Tivoli actuellement). Si ça swappe il faut ajouter de la RAM ou diminuer les JVMs (swapper est plus grave que garbager souvent). dewavrin@yahoo.com / adewavrin@itboosters.fr
Conclusions 1. Malheureusement il n’existe pas de réglage universel car chaque application est différente en termes : d’utilisation de la mémoire (nombre et longévité des objets utilisés) d’attendus (latence acceptée, temps de calcul maximum) d’infrastructure utilisée (mémoire disponible de la machine, nombre de cpus…) 2. Pour le GC « major », c’est-à-dire de la zone « old », il faut accepter un défaut, au choix: forte utilisation du cpu (GC parallèle) longues pauses (GC classique) fragmentation de la mémoire nécessitant un petit surdimensionnement (GC CMS) 3. Le réglage suivant choisi pour la plateforme d’échange a divisé les pauses par 10 au moins : GC minor en parallèle, GC major en CMS heap de 2Go, new de 1/3, survivors de 1/6 Seul l’OSB a pour l’instant été laissé à 3Go suite à un incident. On a encore des pauses de temps en temps, dues à la fragmentation (20 fois moins). 4. Des bonnes pratiques de développement (attention aux instanciations inutiles ou implicites) peuvent être promues, mais dans le cas de progiciels c’est parfois difficile à imposer. Il ne reste alors que le tuning technique pour faire fonctionner l’appli. 5. Le futur algorithme de garbage (G1) est prometteur, il faudra l’évaluer avec java 7.
Annexe : Plateforme d’échanges (production) Une cinquantaine de JVMs en weblogic 10.3.4 / 10.3.6 Basées sur le jdk 1.6 de Sun
Sources http://www.javaworld.com/javaworld/jw-01-2002/jw-0111-hotspotgc.html http://artiomg.blogspot.fr/2012/06/tracking-excessive-garbage-collection.html http://blog.ragozin.info/2011/10/java-cg-hotspots-cms-and-heap.html http://lokeshtc.blogspot.fr/2012/11/jvm-heap-tuning-on-sun-jdk-using-cms.html dewavrin@yahoo.com / adewavrin@itboosters.fr http://blog.mgm-tp.com/2013/03/garbage-collection-tuning/ http://java.dzone.com/articles/how-tame-java-gc-pauses http://www.slideshare.net/ludomp/gc-tuning-in-the-hotspot-java-vm-a-fisl-10-presentation