Maison  >  Article  >  Java  >  Explication détaillée de l'optimisation du garbage collection pour les applications Java critiques (partie 2)

Explication détaillée de l'optimisation du garbage collection pour les applications Java critiques (partie 2)

黄舟
黄舟original
2017-03-23 11:03:441058parcourir

Optimisation du garbage collection pour les applications Java critiques (Partie 1)

Collecteur Parallel Mark Sweep (CMS)

Le garbage collector CMS est le premier collecteur à faible latence largement utilisé. Bien qu'il soit disponible en Java 1.4.2, il n'est pas très stable au début. Ces problèmes n’ont été résolus qu’avec Java 5.

Comme le montre le nom du collecteur CMS, il utilise une méthode parallèle : la plupart du travail de recyclage est effectué par un thread GC, qui est exécuté en parallèle avec le thread de travail qui gère les demandes des utilisateurs. Le processus de recyclage original à guichet uniquetop-le monde de l'ancienne génération est divisé en deux pauses plus courtes pour arrêter le monde et 5 étapes parallèles. Pendant ces phases parallèles, le thread de travail d'origine s'exécute comme d'habitude (sans être mis en pause).

Utilisez les paramètres suivants pour activer le recycleur CMS :

-XX:+UseConcMarkSweepGC

Appliquer à nouveau le programme de test ci-dessus (et augmenter la charge) donne les résultats suivants :

Figure 4 Comportement GC de la JVM avec une taille de tas optimisée et utilisant CMS en 50 heures (-Xms1200m -Xmx1200m -XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6 -XX: + UseConcMarkSweepGC))

Comme vous pouvez le constater, la pause de 8s du GC ancienne génération a disparu. Désormais, il n'y a que deux pauses dans le processus de collecte de l'ancienne génération (la précédente entraînait 5 pauses en 50 heures), et toutes les pauses durent moins d'une seconde.

Par défaut, le collecteur CMS utilise ParNew (algorithme GC) pour gérer la collection de nouvelle génération. Si ParNew est exécuté avec un CMS, ses pauses seront un peu plus longues que sans CMS en raison de la coordination supplémentaire requise entre eux. Par rapport aux derniers résultats des tests, ce problème se retrouve dans la légère augmentation du temps de pause moyen de la nouvelle génération. Des valeurs aberrantes apparaissent fréquemment dans le temps de pause de la nouvelle génération, et ce problème peut également se retrouver ici. Les valeurs aberrantes peuvent atteindre environ 0,5 seconde. Mais ces pauses sont suffisamment courtes pour de nombreuses applications, de sorte que la combinaison CMS/ParNew peut constituer une bonne option d'optimisation à faible latence.

Un sérieux défaut du collecteur CMS est que le CMS ne peut pas démarrer lorsque l'espace ancienne génération est plein. Une fois l'ancienne génération pleine, il est trop tard pour démarrer le CMS ; la VM doit utiliser la stratégie habituelle de "stop-the-world" (les enregistrements de "défaillance du mode simultané" apparaîtront dans les journaux GC) . Afin d'atteindre l'objectif de faible latence, lorsque l'utilisation de l'espace de l'ancienne génération atteint un certain seuil, le collecteur CMS doit être démarré, ce qui est réalisé via les paramètres suivants :

-XX:CMSInitiatingOccupancyFraction=80

Cela signifie qu'une fois l'ancien l'espace de génération est occupé à 80 %, le collecteur CMS fonctionnera. Pour notre application, utilisez simplement cette valeur (qui est la valeur par défaut). Mais si le seuil est trop élevé, une « défaillance du mode simultané » se produira, entraînant des pauses à long terme du GC d'ancienne génération. D'un autre côté, s'il est défini trop bas (inférieur à la taille de l'espace actif), le CMS peut toujours fonctionner en parallèle, ce qui entraîne l'utilisation complète d'un certain cœur de processeur pour le GC. Si le comportement de création d' objets d'une application et d'utilisation du tas change rapidement, par exemple en lançant des tâches spécialisées via des méthodes interactives ou des minuteries, il est difficile de définir une valeur seuil appropriée tout en évitant les deux problèmes ci-dessus.

Shadow of Fragmentation

Cependant, l'un des plus gros problèmes du CMS est qu'il ne range pas l'espace de tas de l'ancienne génération. Cela crée une fragmentation du tas qui, au fil du temps, peut entraîner une grave dégradation du service. Deux facteurs peuvent en être la cause : un espace restreint sur l’ancienne génération et un recyclage fréquent des CMS. Le premier facteur peut être amélioré en augmentant l'espace du tas d'ancienne génération, plus grand que l'espace requis par le collecteur ParallelGC (je l'ai augmenté de 1024M à 1200M, comme vous pouvez le voir sur les premières images). Le deuxième problème peut être optimisé en divisant de manière appropriée l’espace de chaque génération, comme mentionné précédemment. Nous pouvons effectivement voir à quel point cela peut réduire la fréquence des GC d’ancienne génération.

Afin de prouver qu'il est important d'ajuster raisonnablement la taille du tas de chaque génération avant d'utiliser CMS, voyons d'abord comment utiliser le collecteur CMS directement sur la base de la figure 1 (presque pas d'optimisation du tas) si les principes ci-dessus ne sont pas suivis. Que se passe-t-il :

Figure 5 Comportement du GC sans taille de tas optimisée et détérioration des performances causée par la fragmentation de la mémoire après l'utilisation du CMS (à partir de l'heure 14). )

Il est évident que la JVM peut fonctionner de manière stable pendant près de 14 heures sous le test de charge avec ce paramètre (dans un environnement de production et dans des conditions de charge plus faibles, cette phase d'instabilité bénigne peut durer plus longtemps). Ensuite, il y aura soudainement plusieurs longues pauses GC qui occuperont près de la moitié du temps restant. Non seulement le temps de pause de l'ancienne génération atteindra plus de 10 secondes, mais le temps de pause de la nouvelle génération atteindra également plusieurs secondes. Parce que le collectionneur doit passer beaucoup de temps à fouiller l'espace de l'ancienne génération afin de déplacer les objets de la nouvelle génération vers l'ancienne génération.

CMS低延迟优点的代价就是内存碎片。这个问题可以最小化,但是不会彻底消失。你永远不知道它什么时候会被触发。然而,通过合理的优化与监控可以控制它的风险。

G1(Garbage First)回收器的希望

G1回收器设计的目的就是保证低延迟的同时而没有堆碎片风险。因此,Oracle把它作为CMS的一个长期取代。G1可以避免碎片风险是因为它会整理堆空间。对于GC暂停来说,G1的目标并不是使暂停时间最小化,而是设置一个时间上限,使GC暂停尽量满足这一上限值。

在将G1回收器用于测试程序中并与上述其他经典回收器做对比之前,先总结两点关于G1的重要信息。

  • Oracle在Java 7u4中开始支持G1。为了使用G1你应该将Java 7更新到最新。Oracle的GC团队一直致力于G1的研发,在最新的Java更新中(本文编写时最新版本是7u7到7u9),G1的改进很显著。另一方面,G1无法在任何Java 6版本中使用,而且到目前更优越的Java 7不可能向后移植到Java 6中。

  • 前面关于调节各代空间大小的优化对G1来说已经淘汰了。设置各代空间大小与设置暂停目标时间相冲突会使G1回收器偏离原本的设计目标。使用G1时,可以使用“-Xms”和“-Xmx”设置整体的内存大小,也可以设置GC暂停目标时间(可选),对G1来说不用设置其他选项。与ParallelGC回收器的AdapativeSizingPolicy类似,它自适应地调整各代空间大小来满足暂停目标时间。

遵循这些原则后,G1回收器在默认配置下的结果如下:

图6 最小配置(-Xms1024m -Xmx1024 -XX:+UseG1GC)的JVM在G1下26小时内的GC性能

在这个例子中,我们使用了默认的GC暂停目标时间200ms。从图中可以看到,平均时间与这个目标比较吻合,最长GC暂停时间与使用CMS回收器差不多(图4)。G1明显可以很好地控制GC暂停,与平均时长相比,离群值也相当少。

另一方面,平均GC暂停时间要比CMS回收器长很多(270 vs 100ms),而且更频繁。这意味着GC累积暂停时间(也就是GC本身所占总时间)是使用CMS的4倍以上(6.96% vs 1.66%)。

与CMS一样,G1也分为GC暂停阶段和并行回收阶段(不暂停任务)。同样与CMS类似,当堆占用比达到一定门限后,它才启动并行回收阶段。从图6可以看到,1GB的可用内存到目前为止并没有完全使用。这是因为G1的默认占用比门限值要比CMS低很多。也有人指出,一般来说较小的堆空间就可以满足G1的需求。

垃圾回收器的定量比较

下面的表格总结了Oracle Java 7中4种最重要的垃圾回收器在测试中的关键性能指标。在同样的应用程序上,进行相同的负载测试,但是负载的级别不同(由第2列的垃圾创建速率体现)。

表 几种垃圾回收器的比较

所有的回收器都运行在1GB的堆空间上。传统的回收器(ParallelGC、ParNewGC和CMS)另外使用下面的堆设置:

-XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6

而G1回收器没有额外的堆大小设置,并且使用默认的暂停目标时间200ms,也可以显示设置:

-XX:MaxGCPauseMillis=200

从表中可以看到,传统回收器在新生代回收上(第3列)时间差不多。对ParallelGC和ParNewGC来说是差不多的,而CMS实际上也是使用ParNewGC去回收新生代。然而,在新生代GC暂停中,将新生代存活对象移入老年代需要ParNewGC和CMS的协同。这样的协同引入额外的代价,也就导致CMS的新生代GC暂停时间要略长。

第7列是GC暂停所耗费的时间占总时间的百分比,这个值可以很好地反映GC的总时间代价。因为并行GC总时间(最后一列)以及引入的CPU占用代价可以忽略。按前文所述,优化堆大小后老年代GC次数会变得很少,这样第7列的值主要由新生代GC暂停总时间所决定。新生代暂停总时间是新生代暂停(连续)时长(第3列)与暂停次数的乘积。新生代暂停频率与新生代空间大小有关,对传统回收器来说,这个大小是相同的(400MB)。因此,对传统回收器来说,第7列的值或多或少地反映着第3列的值(负载差不多的情况)。

Les avantages du CMS sont clairement visibles dans la colonne 6 : il échange un coût en temps total légèrement plus long contre des pauses GC d'ancienne génération plus courtes (un ordre de grandeur inférieur). Pour de nombreuses applications réelles, il s’agit d’un bon compromis.

Alors, comment le collecteur G1 fonctionne-t-il pour notre application ? Comme on peut le voir dans la colonne 6 (et la colonne 5), le collecteur G1 fait un meilleur travail que le collecteur CMS en réduisant le temps de pause du GC de l'ancienne génération. Mais comme vous pouvez également le voir dans la colonne 7, cela paie un prix très élevé : sous la même charge, le coût total en temps du GC représente 7 %, tandis que le CMS ne représente que 1,6 %.

Dans les articles suivants, j'examinerai les conditions qui font que G1 entraîne un coût de temps GC plus élevé, et j'analyserai également les avantages et les inconvénients de G1 par rapport aux autres collecteurs (en particulier le collecteur CMS). Il s’agit d’un sujet vaste et précieux.

Résumé et perspectives

Pour tous les algorithmes Java GC classiques (SerialGC, ParallelGC, ParNewGC et CMS), il est important d'optimiser la taille de l'espace du tas de chaque génération. Cependant, dans de nombreuses applications pratiques. Le programme n'a pas fait suffisamment d'optimisation raisonnable. Le résultat est des performances d'application insuffisamment optimisées et une dégradation opérationnelle (entraînant une perte de performances et même une suspension du programme pendant un certain temps s'il n'est pas bien surveillé).

L'optimisation de la taille de l'espace du tas de chaque génération peut améliorer considérablement les performances des applications et minimiser le nombre de longues pauses GC. Ensuite, l’élimination des longues pauses GC nécessite l’utilisation d’un collecteur à faible latence. CMS a été (jusqu’à présent) le collecteur à faible latence préféré et efficace. Dans de nombreux cas, un CMS suffira. Avec une optimisation raisonnable, il peut toujours garantir une stabilité à long terme, mais il existe un risque de fragmentation du tas.

Comme alternative, le collecteur G1 est actuellement (Java 7u9) une option prise en charge et disponible, mais il y a encore place à l'amélioration. Ses résultats sont acceptables pour de nombreuses applications, mais ne se comparent pas très bien à ceux du collecteur CMS. Les détails de ses avantages et inconvénients méritent une étude attentive

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn