Comment résoudre l’impact du chargement des classes dynamiques sur les performances Java ?

Par :
fredericmazue
mer, 01/07/2015 - 12:24
Niveau :
Avancé

Pour bien comprendre de quoi il retourne, appuyons-nous sur l'exemple d'un test de charge réalisé par Aftab Alam d'Infosys avec SOASTA pour la simulation de charge, et Dynatrace pour l'analyse des performances de l'application à charge constante.

Le rapport de performances SOASTA ci-dessus offre une vision du comportement de l'application sur toute la durée du test. Des temps de réponse sans cesse croissant face à une charge constante sont généralement le signe d'une fuite de ressources. Mais dans ce cas précis, le rapport de test de charge ne suffit pas, à lui seul, à expliquer pourquoi les performances de l'API de recherche mobile faiblissent graduellement avant de se dégrader brutalement après 6 heures de charge.

Pour comprendre ce  comportement, observons les résultats de Dynatrace, qui capture les données de chacune des requêtes envoyées par SOASTA à l'application sur l'ensemble du test de charge. A la lecture du schéma de répartition ci-dessous, qui représente l'impact de chaque élément d'une transaction dans le temps de chargement global, on peut observer que cette baisse des performances résulte d'un temps de chargement excessif des classes Java. En particulier après la sixième heure.


En rose, le temps de chargement des classes Java, de plus en plus long, surtout après 6 heures.

Avant de détailler les différentes étapes du diagnostic et de résolution du problème, et une fois n'est pas coutume, commençons par la fin en dévoilant dès maintenant l'origine du problème, identifiée grâce aux purepaths Dynatrace et les logs de l'application : les temps de chargement excessifs des classes Java proviennent de la librairie XSLT dont le rôle est de transférer les réponses XML d'un service Web de back-end dans le format requis par l'application.

La dégradation brutale des performances correspond au franchissement du seuil des 2,5 millions de classes dynamiques créées, chargées et déchargées, et qui intervient au bout de 6 heures environ. Un cas d'usage que l'on ne peut généralement retrouver qu'à l'occasion d'une simulation de charge constante. Néanmoins, la création d'un nombre excessif de classes Java est un problème que l'on rencontre également à l'occasion de tests unitaires, d'intégration et même manuels : il suffit pour s'en convaincre de s'attarder sur ces mesures. Ainsi, si des classes Java dynamiquement créées ont une durée de vie éphémère lors des transactions, il est indispensable de se demander pourquoi et à quoi elles sont destinées 

Maintenant que nous connaissons l'origine du problème, détaillons les différentes étapes de diagnostic et de résolution du problème. A noter que l'essai gratuit proposé par Dynatrace permet de tester en local les performances de ses applications, sur un serveur d'intégration continue ou dans un environnement de test.

Étape 1 – Utiliser les bons indicateurs

A partir du schéma de répartition des temps de chargement ci-dessus, on constate que le ralentissement des performances résulte du chargement des classes Java. Ici, deux indicateurs sont donc à surveiller plus particulièrement : le nombre de classes chargées et le nombre de classes déchargées.

Ainsi, le graphique suivant montre que les classes actives qui restent chargées sont assez constantes. A l'inverse, le nombre total de classes déchargées connaît une croissante alarmante jusqu'à atteindre 2,5 millions en 6 heures. Soit un important nombre de classes dynamiques créées pour très courte période, ce qui est bien connu avec XML et XSLT, mais également sur OR-Mappers.


Augmentation excessive du nombre de classes déchargées vs constance du nombre de classes actives.

Étape 2 – Identifier le responsable

Grâce à Dynatrace, trouver la partie du code responsable de la génération de toutes ces classes dynamiques est assez simple. Le rapport PurePath de Dynatrace ci-dessous, qui détaille précisément le processus d'exécution du code, indique clairement quel composant fait appel au ClassLoader pour la création de nouvelles classes.

Pour aller plus loin et déterminer le nom des classes générées, deux options sont possibles : utiliser les journaux d'événements de la machine virtuelle Java pour obtenir la liste des classes, ce qui peut être fastidieux. Ou, dans Dynatrace, créer un capteur personnalisé sur la commande defineClass pour obtenir le nom des classes générées directement dans le rapport PurePath.

Étape 3 – Résolution du problème grâce à JSON

Dans la mesure où la transformation par XSLT ne fonctionne pas sur le long terme, il est possible de convertir les réponses XML en JSON en utilisant org.json. Dès lors, la librairie XSLT n'est plus utilisée, supprimant en même temps la création et l'élimination de toutes ces classes.

Étape 4 – Validation de la résolution du problème

Pour vérifier que le problème est corrigé, il suffit de relancer une simulation de charge identique à la première, en s'appuyant sur les mêmes indicateurs : le nombre de classes chargées et déchargées. Le graphique suivant représente ces 2 métriques, dont les chiffres reflètent le comportement normal d'une application en termes de chargement de classes.

Tandis que le schéma de répartition des temps de chargement ci-dessous prouve que le chargement des classes n'impacte plus les performances de l'application, et en particulier après 6 heures de charge constante.