Trucs et astuces pour l’optimisation des performances en Java/JEE

Note : cet article s’adresse aux développeurs et uniquement à eux, et pas aux gens de l’exploit’ qui devront tuner les paramètres de la JVM une fois l’appli en production.

D’ailleurs même si dans certains cas ça peut aider notamment pour le choix de l’algorithme du GC et la quantité de mémoire, globalement le tuning de la JVM devrait s’arrêter là dans le cas général. Au-delà, bien souvent, c’est bien souvent que c’est directement le code de l’application qui a un problème.

De nombreux programmes présentent de mauvaises performances, et parfois on se demande comment on pourra les corriger. La bonne nouvelle est qu’il existe à cet effet des outils à la disposition du programmeur permettant de détecter les problèmes, la mauvaise est qu’il n’y a pas de règle générale pour les fixer. Cela nécessite en effet une connaissance de l’application et en même temps une bonne connaissance de la JVM pour savoir quoi faire dans le cas où un problème est détecté. Par exemple pour la classe java.lang.String, les méthodes replace et split seront souvent avantageusement remplacées par des appels à indexOf bien placés. Aussi cet article ne prétend pas vous fournir de recettes pour corriger les problèmes de performance, mais uniquement pour les détecter pour ensuite jouer aux apprentis sorciers. Ah oui et si vous faites ça sur une application destinée à aller en production, ne faites rien sans savoir exactement ce que vous faites ! Un programme est en effet un ensemble d’équilibres usage CPU/usage mémoire et les optimisations consistent bien souvent à jouer sur l’un et l’autre, pour atteindre le meilleur équilibre possible. Aussi il y a des risques réels de tout casser, donc si vous n’êtes pas sûr ne le faites pas !

Démarche générale pour l’optimisation

L’optimisation de logiciels est un processus itératif. La première chose à faire est d’isoler la fonctionnalité que vous souhaitez voir optimisée. S’il y en a plusieurs, procédez toujours fonctionnalité par fonctionnalité, rien ne sert d’en faire plusieurs à la fois. Par ailleurs il s’agit d’un processus itératif. En d’autres termes dans un premier temps vous allez détecter la méthode ou la ligne qui pose problème, le corriger, et vous allez refaire une mesure. Là vous allez trouver une autre méthode ou ligne qui pose problème, la corriger, et ainsi de suite.

La première chose à faire est de se constituer un jeu de données suffisamment conséquent pour avoir quelque chose de mesurable. En effet, supposons que vous souhaitiez optimiser un explorateur de fichiers. Si vous visualisez le contenu d’un dossier ne comportant qu’un seul fichier, ce sera probablement instantané et vous n’observerez rien. Par contre si vous avez 100000 fichiers dans ce même dossier, là vous observerez des choses.

De même, si le comportement ne se produit que pour un jeu de données présentant une caractéristique particulière, par exemple un ralentissement uniquement dans le cas où les fichiers sont des images, mettez de nombreuses éléments présentant cette caractéristique dans votre jeu, ici des images.

L’outil de base : les stack traces

Les stack traces sont un outil très basique pour surveiller l’exécution d’un programme, mais qui présente néanmoins l’avantage d’être extrêmement léger à l’exécution et qui est parfois suffisant. L’idée est simple : lorsque votre programme s’exécute, vous faites afficher à votre JVM les piles d’exécution des différents threads de celui-ci de manière régulière. Si une stack apparaît trop souvent, vous tenez probablement votre coupable.

Pour les afficher, l’outil nécessaire est jstack. Par ailleurs, votre programme devra avoir été compilé avec les symboles de débogage pour que les stacks soient exploitables. C’est le cas par défaut avec javac, mais si vous utilisez ant assurez-vous que l’instruction javac contient bien le flag true.

Ensuite, il vous faut repérer l’id du processus Java que vous voulez mesurer, puis lancer la commande suivante (sous Linux) pour afficher les stacks – ici toutes les 10 secondes : while true; do jstack <b>votre_process_id</b>; sleep 10; done, et enfin lancer l’opération que vous voulez mesurer.

Un outil nettement plus avancé : le profiler

Depuis les débuts de la JVM, de nombreux profilers sont apparus. Ces outils permettent de contrôler tous les aspects d’un programme en terme de performance, en particulier le temps d’exécution passé dans chaque méthode, la consommation mémoire et l’usage du GC notamment. Parmi les plus connus on trouve Yourkit Java Profiler, JProfiler ou encore VisualVM. Ce dernier est depuis quelques années fourni directement dans la JDK, cela dit la version proposée est souvent obsolète. Dès lors, les utilisateurs de Windows ont plutôt intérêt à le télécharger ici, quant aux utilisateurs de Linux (distributions à base Debian) ils peuvent installer le paquet jvisualvm. A noter que pour l’utilisation d’un profiler, là encore, il faut inclure les symboles de debug au moment de la compilation de vos fichiers.

La fenêtre générale de l’application se présente comme sur l’image ci-dessous, avec la liste des processus Java sur la gauche. On note qu’il est possible de monitorer des applications à distance via JMX, mais dans cet article on ne va pas le faire.

Faites dans cette liste un clic droit sur l’application qui vous intéresse et cliquez sur Sample. La fenêtre de profiling s’ouvre, cf. image ci-dessous. A noter que l’option Profile existe également mais qu’il vaut mieux éviter de l’utiliser, car elle a une influence très notable sur les performances de l’application mesurée. Ensuite, cliquez sur settings pour sélectionner les packages que vous voulez profiler. En effet il n’y a aucun intérêt à profiler les classes de la JVM, mais uniquement vos classes. Dans l’exemple ci-dessous, j’ai choisi de profiler les classes du package org.gojul.fourinaline ainsi que toutes celles de ses sous-packages.

Ensuite, vous pouvez joyeusement fermer la vue settings en cliquant sur « Settings », puis lancer un profiling au niveau du processeur en cliquant sur le bouton CPU, cf. image ci-dessous. Si votre application passe beaucoup de temps dans les calculs, il faut trier les méthodes par « Self Time (CPU) », alors que si c’est une application qui utilise une source de données externe comme des fichiers ou une base de données il faut trier par « Self Time ». Vous pouvez alors lancer votre mesure.

Là vous pourrez avoir une bonne idée de la ou des méthodes prenant le plus de temps d’exécution, cf. image ci-dessous. Pour avoir plus de détails encore vous pouvez cliquer sur « Snapshot ». Cela vous ouvre une nouvelle vue dans laquelle vous pouvez voir le temps passé dans chaque méthode (lignes « Self time ») ainsi que le temps passé dans les méthodes appelées par ces méthodes.

De la même manière, vous pouvez aussi mesurer l’usage mémoire de vos structures de données, même si cette vue est plus difficile à exploiter dans le cas de VisualVM. Cela dit elle peut être très utile par exemple dans le cas où votre application doit traiter des valeurs numériques en grand nombre, mais qu’elle les stocke sous forme de String, bien moins efficaces. Mais l’optimisation mémoire des structures de données se fait là encore au cas par cas et nécessite une bonne connaissance de la JVM.

Surveillez l’occupation du GC

Dans la vue Monitor de VisualVM vous pouvez surveiller l’utilisation du GC. Si vous remarquez que celui-ci occupe plus de 5% du temps CPU ou que vous voyez de nombreux cycles GC durant l’exécution de votre opération, il est urgent d’intervenir sur votre code en particulier au niveau des allocations de vos objets, et envisager des solutions alternatives aux new. Cela dit l’activation des logs du GC au lancement de votre JVM sera encore plus efficace pour observer les problèmes à ce niveau.

Besoin de tester ton niveau en développement informatique ?

Cet article vous a plu ? Vous aimerez sûrement aussi :

Julien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date !). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.

Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.

Son Twitter Son LinkedIn

gojul

Recent Posts

MICI au travail : Le handicap invisible qui révèle des forces insoupçonnées

Les maladies inflammatoires chroniques de l’intestin ou "MICI" sont invisibles, mais leurs impacts sur la…

2 jours ago

Exploiter les NPUs pour de l’IA embarquée dans les applis webs

Depuis l'été, j'ai un Pixel qui intègre à la fois un TPU (Tensor Processing Unit)…

6 jours ago

Qcm saison hiver 2024 : toutes les infos.

On se retrouve dans un nouvel article avec toutes les infos sur cette nouvelle saison…

3 semaines ago

L’inclusion numérique est essentielle.

Pourquoi l’inclusion numérique est essentielle : le point avec Mathieu Froidure. Dans un monde de…

4 semaines ago

Communauté Tech et féminine : Interview avec Helvira de Motiv’her

Elles sont passées où les femmes dans la tech ? Entre le manque de représentation…

1 mois ago