Didacticiel pour le Profileur NetBeans
Cet article fait partie de la série d'articles soumis
par la communauté NetBeans dans le cadre du concours "Win
With NetBeans".
Le profileur NetBeans est un outil puissant qui fournit des
informations importantes sur le comportement d'une application. Tout
en n'introduisant que très peu de dégradation de
performance, le profileur NetBeans permet de surveiller la
performance d'utilisation du processeur et de la mémoire. Ce
didacticiel vous démontre l'utilisation du profileur
NetBeans pour:
Certaines portions de ce didacticiel sont tirées d'un
article
daté de juin 2004 dans le Java Developer's Journal sur
la technologie JFluid qui constitue la technologie sous-jacente du
profileur NetBeans. Ces portions sont reprises avec l'accord du Java
Developer's Journal.
Ce didacticiel dure environ une heure.
Démarrage rapide avec l'EDI NetBeans 4 pour les
applications Web
Si vous n'êtes pas familiers de l'EDI NetBeans 4, consultez
Guide Rapide pour Applications Web avec l'EDI NetBeans 4.0.
Une fois la lecture de ce document faite, ce didacticiel pourra parfaire
vos compétences!
Mise en place de l'environnement
Installation du profileur
Le profileur NetBeans est encore en version bêta et disponible au travers d'un
téléchargement séparé. Pour suivre ce didacticiel, il vous faut
installer la version la plus récente du profileur NetBeans
en suivant ces instructions
d'installation.
Configuration de Tomcat
Pour profiler une application web, il
est nécessaire d'utiliser le serveur utilisé avec
la JVM fournie par le profileur. Aussi, pour que l'on puisse profiler
une application s'exécutant dans Tomcat, il faut
procéder à une légère modification du script de
démarrage pour que la variable d'environnement JAVA_HOME
soit correctement positionnée. De plus, il vous faut
"compléter" la JVM fournie par le profileur en lui rajoutant
des répertoires issus du JDK 1.4.2. Pour tous ces
détails, il suffit de suivre les deux étapes dans les
Instructions
Tomcat qui permettent de se brancher et de profiler une application.
Obtenir les fichiers sources
Ce didacticiel utilise une application
dérivée du projet HelloWeb créé dans la documentation
Guide Rapide pour Applications Web avec EDI NetBeans 4.0.
Les modifications apportées sont liées
à l'ajout d'une servlet de gestion des requêtes
qui crée un JavaBeans avant de passer la main
à la JSP qui génère la réponse. La servlet est
volontairement mal rédigée pour mettre en valeur les
capacités du profileur NetBeans.
L'ensemble des fichiers se trouvent
dans l'archive ProfilingTutorial.zip.
-
Sur votre système de fichiers, créez un
répertoire qui contient les fichiers
décompressés de l'archive. A partir de
maintenant nous y ferons référence
sous le nom de $UNZIPHOME.
-
Cliquez ici
pour télécharger l'archive ProfilingTutorial.zip file.
-
Utilisez un utilitaire qui décompresse les fichiers
contenus dans l'archive ProfilingTutorial.zip
vers le répertoire $UNZIPHOME.
$UNZIPHOME
possède maintenant des répertoires web
and src. Le répertoire web
contient les deux fichiers JSP et les descripteurs de
déploiement. Le répertoire src
contient les fichiers source de la servlet et des JavaBeans.
Création du projet
Création d'un projet web à partir des fichiers source fournis.
-
Choisisez File > New Project (Maj-Ctrl-N). Sélectionnez la
catégorie Web. Choisissez ensuite "Web Project with Existing
Sources" et cliquez sur Next.
-
Pour "Location", cliquez sur "Browse" pour sélectionner la racine
de l'application web. Il s'agit ici de $UNZIPHOME,
le répertoire dans lequel se trouve le contenu
décompressé du fichier ProfilingTutorial.zip.
-
Pour "Project Name", saisissez DidacticielProfileur
et cliquez sur Next.
-
Choisissez enfin "Finish". L'EDI crée alors le répertoire
projet $UNZIPHOME/nbproject
et le fichier $UNZIPHOME/build.xml.
Le projet DidacticielTutorial est alors ouvert dans l'EDI.
Vous pouvez voir sa présentation logique dans l'onglet "Projects"
et l'ensemble de ses fichiers dans l'onglet "Files".
-
Cette dernière étape n'est nécessaire que lorsque NetBeans
fonctionne avec le JDK 5.
Avec un clic-droit sur le noeud DidacticielProfileur
dans la vue "Projects", choisissez "Properties".
Dans la partie gauche, sélectionnez "Build", puis "Compiling Sources".
Enfin, dans "Additional Compiler Options" saisssez
-source 1.4 -target 1.4 et choisissez OK.
Exécution du projet
-
Il vous faut maintenant noter les identificateurs de tous les processus
des machines virtuelles Java en cours d'exécution.
Ces identificateurs seront nécessaires pour identifier
la JVM utilisée par Tomcat.
Sous Unix, la commande suivante permet de
récupérer l'identificateur d'un processus:
ps -eaf | grep java en ligne de commande:
$ ps -eaf | grep java
gs 8132 8104 0 12:49:32 pts/4 0:51 /export/home/gs/tools/java/jdk50_01/bin/java -Djdk.home=/export/home/gs/tools/j
gs 8104 8101 0 12:49:31 pts/4 0:00 /bin/sh ./../platform4/lib/nbexec --jdkhome /export/home/gs/tools/java/jdk50_01
gs 8215 8093 0 12:50:59 pts/4 0:00 grep java
Sous Windows, il est possible de récupérer l'identificateur d'un
processus à l'aide du Gestionnaire des tâches de Windows tout en ayant pris
la peine de faire apparaître les identificateurs. Pour faire apparaître
la colonne PID (Identificateur de Processus), choisissez "Affichage",
puis "Sélectionner les colonnes..." et enfin cocher la case
"PID". L'illustration ci-dessous indique qu'une seule instance de
java.exe est en cours d'exécution et que son identificateur est 2848.
(NdT: avec JDK 5.0, l'exécutable jps
(situé dans le répertoire bin
du JDK) permet de récupérer simplement les
identificateurs de toutes les JVM 5.0 quelque soit le
système d'exploitation utilisé.
-
Dans l'onglet "Projects",
cliquez sur le noeud DidacticielProfileur
et choisissez "Run Project". Ceci indique à l'EDI
d'exécuter le projet en démarrant Tomcat et en
affichant la page index.jsp dans une fenêtre de navigateur.
-
Vérifiez que Tomcat a bien démarré en cliquant sur
"Bundled Tomcat" dans l'onglet "Output" qui affiche la sortie standard
du serveur. Remontez en haut de cette fenêtre d'information
Tomcat pour vérifier que le serveur a bien été démarré avec la JVM du
Profileur NetBeans. Pour cela, assurez-vous que la ligne qui
débute par Using JAVA_HOME pointe bien vers la
JVM du Profileur NetBeans. Dans le cas inverse, ou si Tomcat ne
démarre pas, se référer aux
instructions de ci-dessus de Configuration de Tomcat.
Un exemple de sortie standard sous Windows est
présenté ci-dessous .
-
A ce stade, une fenêtre du navigateur doit être ouverte et afficher
le contenu de l'adresse http://localhost:8084/DidacticielProfileur/.
Pour vérifier le bon fonctionnement de l'application web,
tapez NetBeans User dans le champ 'name' et cliquez sur le bouton 'OK'. Le
résultat doit ressembler à l'illustration ci-dessous.
Surveillance du comportement de l'application
Se brancher sur une application en cours d'exécution
Maintenant que Tomcat fonctionne
correctement avec la JVM fournie par le profileur NetBeans, il est
possible de se "brancher" avec les outils de surveillance
du comportement de l'application de l'EDI.
-
Déterminez l'identificateur de processus de la JVM utilisée par Tomcat.
A ce stade, il doit y avoir un processus Java
(java.exe sous Windows) supplémentaire par rapport à
la dernière fois.
Le nouveau processus Java doit correspondre à Tomcat
(NdT: jps et son compagnon jinfo sont susceptibles de fournir
un peu plus d'information sur un processus donné).
Notez l'identifiant ainsi récupéré.
-
Sélectionnez le menu "Profile", puis "Attach Profiler".
Pour "Working Directory", saisissez le nom complet du répertoire bin
de Tomcat.
A titre d'exemple, sous Windows, la valeur doit ressembler à ceci:
C:\Program Files\netbeans-4.0\nb4.0\jakarta-tomcat-5.0.28\bin
Et sous Unix :
/export/home/gs/tools/netbeans/40/nb4.0/jakarta-tomcat-5.0.28/bin
-
Cliquez maintenant sur "Monitor Application" en vous assurant que l'option
"Enable Threads Monitoring" n'est pas sélectionnée.
-
Cliquez sur le bouton "Attach" et sélectionnez "Manually enter PID of the
application". Pour "PID" saisir l'identificateur de processus de la JVM
utilisée par Tomcat. Choisissez OK.
Interprétation des graphiques
Le profileur NetBeans présente trois graphiques comme illustré ci-dessous.

Le graphique de gauche est le plus simple à comprendre.
La partie rouge indique la quantité de mémoire allouée par la JVM
avec une mise à jour toutes les secondes. La partie violette correspond
à la quantité de mémoire réellement utilisée.
Dans l'exemple ci-dessus, la mémoire allouée est de presque 14Mo dont
environ 4Mo sont utilisés par des objets Java.
Le graphique de droite est également simple à comprendre. Il indique
simplement le nombre de threads actifs dans la JVM mis à jour toutes les
secondes.
Le graphique du ùimmieu est le plus intéressant car il
présente deux statistiques importantes.
-
La ligne bleue correspond au temps passé par la JVM
à nettoyer sa mémoire avec le ramasse-miettes.
Son axe est à droite du graphe. Ce temps
nécessaire à la gestion de la mémoire
n'est pas disponible pour l'application. Donc, si la ligne bleue
indique un pourcentage important de temps passé dans cette
gestion de mémoire, il est peut-être
nécessaire d'envisager une taille de tas [heap] plus grande
(cf. documentation
du paramètre -Xmx )
ou bien d'utiliser une autre
stratégie de ramasse-miettes.
-
La ligne rouge correspond aux survies de générations.
Son axe est sur la gauche du graphe.
Cette valeur est l'âge des objets Java contenus dans la mémoire de la JVM.
L'âge est ici défini comme le nombre de passage de ramasse-miettes auquel
un objet a survécu. Lorsque la valeur des survies est
faible, cela indique que la plupart des objets présents en
mémoire le sont depuis une durée similaire.
Cependant, si cette valeur augmente rapidement dans le temps, cela
indique que l'application alloue de nouveaux objets tout en gardant des
références vers de nombreux anciens objets. Si ces objets "anciens" ne sont
plus réellement nécessaires, alors nous avons à faire à une rétention de
mémoire (parfois appelée fuite mémoire).
En appuyant sur le bouton
l'EDI présente des version plus étendues de ces
trois graphiques dans sa page principale.
Détermination de la quantité de
temps processeur utilisé par une méthode
Passage au mode d'analyse de performance
A ce stade, le profileur est rattaché à la JVM de Tomcat, mais il ne fournit
que des informations macroscopiques. Pour obtenir plus de détails
sur la performance d'une méthode donnée (ou de
plusieurs méthodes) de votre application, il vous faut
modifier les paramètres du profileur.
-
Appuyez sur le bouton
ou choisissez "Profile", puis "Modify Profiling".
-
Appuyez sur le bouton "Analyze Performance".
-
Cochez la case "Part of Application" et appuyez sur le bouton "Select" qui se
situe à coté.
-
Appuyez sur le bouton "Add".
-
Appuyez sur le bouton "Select". Le profileur affiche alors l'arbre de
sélection des classes.
-
Ouvrez le paquetage de sources en cliquant sur son signe plus.
-
Ouvrez le noeud org.me.hello.
-
Ouvrez le noeud RequestHandler.java.
-
Sélectionner le noeud RequestHandler et appuyez sur le bouton OK.
-
Les méthodes de la classes RequestHandler sont maintenant
présentées dans la fenêtre "Select Methods" comme indiqué ci-dessous.
Sélectionnez la méthode processRequest et appuyez sur le boutton OK.
-
Sélectionnez la méthode processRequest
en cliquant dessus dans la liste des méthodes racine [root].
Appuyez ensuite sur OK. Vous venez de sélectionner la
méthode processRequest
comme étant la racine de l'analyse de performance. Ceci
signifie que la méthode processRequest
et toutes celles qu'elle invoque à son tour (et ainsi de
suite) seront surveillées. En partant de processRequest,
le profileur procède à la construction du graphe
d'appel pour en déduire les méthodes à
observer. Le restant de l'application est inchangé et son
exécution continuera à se faire sans
dégradation de performance.
-
Appuyez sur le bouton OK de la fenêtre "Modify Profiling".
Exécution de la méthode instrumentée
Maintenant que vous avez positionné
processRequest comme méthode racine, il vous faut
exercer la partie de l'application qui sollicite cette
méthode racine. Dans ce cas précis, c'est assez
simple car la méthode processRequest
gère toutes les requêtes de la page index.jsp.
-
Dans votre navigateur, cliquez sur le bouton "Back" pour revenir
à la page http://localhost:8084/ProfilingTutorial/.
-
Cliquez sur le bouton OK pour soumettre un nouveau nom d'utilisateur.
La réponse sera un peu plus lente à venir du fait de
l'instrumentation de la méthode processRequest.
-
Une fois que Hello, NetBeans User! s'affiche dans le
navigateur, cliquez sur le bouton
ou choisissez "Profile", puis "Get Current Results".
Interprétation des graphiques de performance
Le profileur affiche les derniers résultats en cours comme illustré ci-dessous.
La fenêtre supérieure présente le graphe d'appel des méthodes en
commençant par la méthode racine. Le temps d'exécution de la
méthode processRequest est de 168 milli-secondes.
Cependant, très peu de temps est réellement passé dans la méthode
processRequest elle-même. La sixième ligne dans la fenêtre indique
que le "self time" de la méthode processRequest n'est que de 0.421 ms.
L'essentiel du temps est passé dans les méthodes appelées par
processRequest. Plus précisément, la méthode forward
représente 84.7% du temps total d'exécution. Ceci n'est pas surprenant étant
donné ce que l'on demande à la méthode forward.
La puissance du profileur NetBeans est de pouvoir vous aider à identifier
les goulots d'étranglement dans votre code là où vous ne les attendiez pas et qui
risquent de limiter la montée en charge de votre application. On peut également
noter que la méthode createUniqueID représente 11.3% du temps total d'exécution.
En cliquant sur le signe plus du noeud createUniqueID cela permet
d'investiger plus en profondeur pour savoir où est passé ce temps.
Si l'on examine le code source de createUniqueID
on découvre un algorithme particulièrement inefficace qui doit être revu.
La fenêtre inférieure est une description à plat de la valeur de "self time"
de chaque méthode présente dans le graphe.
Surveillance de la création d'objets
Basculement sur le mode d'analyse de la mémoire
Le profileur est capable de faire une analyse détaillée (aussi appelée
instrumentation) de l'utilisation des ressources processeur
[CPU] ainsi que de l'utilisation de la mémoire, mais il ne
peut pas faire les deux au même moment. Pour obtenir les
détails sur l'allocation et la destruction par le
ramasse-miettes des objets de la JVM, il est nécessaire de
modifier les paramètres du profileur.
-
Cliquez sur le bouton
ou choisissez "Profile", puis "Modify Profiling".
-
Cliquez sur le bouton "Analyze Memory Usage".
-
Cochez la case "Record both object creation and garbage collection".
-
Cliquez sur OK.
Exécution de l'application à analyser
Maintenant que nous avons basculé
vers le mode d'analyse de la mémoire, il nous faut réexécuter l'application
pour comprendre si sa consommation mémoire est normale et efficace.
-
Dans une fenêtre de navigateur, revenez en arrière sur
la page http://localhost:8084/ProfilingTutorial/.
-
Appuyez sur le bouton "OK" pour soumettre de nouveau le nom d'utilisateur
"NetBeans User".
-
Une fois Hello, NetBeans User! affiché dans le navigateur,
appuyez de nouveau sur le bouton de retour en arrière pour revenir sur cette page.
-
Répétez ces étapes 2 et 3 neuf fois. Il est important pour les
besoins de la démonstration que le même nom
d'utilisateur soit à chaque fois utilisé.
-
Cliquez sur le bouton
ou choisissez "Profile", puis "Get Current Results".
-
Le profileur affiche les derniers résultats de
mémoire dans un tableau. Cliquez sur la colonne "Class Name"
pour trier les lignes selon le nom des classes.
-
Naviguez vers le bas jusqu'à la ligne de la classe org.me.hello.NameHandler.
Interprétation des graphiques de la mémoire
L'illustration ci-dessous présente les statistiques de la classe org.me.hello.NameHandler.

Les colonnes fournissent des informations sur l'allocation de l'objet et
son occupation mémoire.
-
"Total alloc. obj." (à droite) est simple à comprendre.
Il s'agit du nombre total d'objets instanciés pour la classe
en question. Pour la classe NameHandler,
cette valeur est de 10, ce qui correspond au nombre de fois ou vous
avez appuyé sur OK dans l'application.
-
"Allocated Objects" correspond au nombre d'objets que le profileur surveille. Dans
cet exemple, sur les 1 instances de NameHandler
créées, le profileur n'en surveille qu'une seule.
Par défaut, ce nombre est environ 10% du nombre total
d'objets alloués (les valeurs que vous observez seront donc
peut-être légèrement
différentes). En ne surveillant qu'un sous-ensemble des
objets créés, le profileur est capable de
réduire de manière très sensible la
dégradation de performance de la JVM liée
à toute technique d'instrumentation. L'application
s'exécute quasiment à vitesse réelle.
-
"Live Objects" représente le nombre d'objets alloués
dans le tas [heap] de la JVM et qui par conséquent occupent
de la mémoire.
-
Les deux colonnes "Live Bytes" présentent la quantité
de mémoire utilisées par les objets
décrits ci-dessus. La première colonne propose un
graphe, la seconde une version texte.
-
La valeur "Age Average" est calculée en utilisant "Live
Objects". L'âge de chaque objet correspond au nombre de
passage du ramasse-miettes auxquels il a survécu. La somme
de tous les âges divisé par le nombre d'objets
correspond la moyenne d'âge [Age Average].
-
La valeur "Generations" est calculée à partir des "Live Objects". La
définition de la moyenne d'âge reste la
même et "Generations" est le nombre d'âges
différents répertoriés pour tous les objets.
Cette information doit vous permettre
de remonter à la source d'une rétention de mémoire.
La valeur "Generations" est la même que "Surviving
Generations" décrite dans le paragraphe Interprétation
des graphiques, mais ramenée à une seule classe.
Cette meilleure granularité doit vous aider à
identifier les objets qui occupent inutilement de la mémoire.
Dans notre exemple, il n'est pas nécessaire d'observer "Generations" pour identifier
le problème dans la classe NameHandler. La valeur
"alloc. obj." est 10. Le principe du code de la servlet
était de réutiliser des instances d'objets NameHandler
pour y stocker le nom saisi par l'utilisateur. Il est clair ici que
cela ne fonctionne pas. Le bug provient de la méthode
processRequest : la clé utilisée pour vérifier l'existence d'un élément
dans la Map n'est pas la même que celle utilisée pour stocker l'élément.
Le résultat est une rétention de mémoire relativement classique en Java :
un élément placé dans un objet Map est aussitôt oublié.
Le profileur fournit également une autre fonctionnalité qui permet
d'identifier l'endroit dans votre code où un objet donné est alloué.
Cela peut permettre de comprendre pourquoi un objet est toujours référencé
en mémoire. Pour cela, le profileur propose des graphes d'appel de méthodes
pour toute classe. Il suffit de
sélectionner "Show Allocations Stack Traces" dans le
menu contextuel (clic-droit) pour n'importe quelle classe du tableau.
Pour mettre fin à la session, choisissez "Profile", puis "Detach".
Pour aller plus loin
L'aide en ligne du Profileur vous propose de l'aide supplémentaire sur ces
différents sujets:
-
Il est possible de filtrer les données affichées
pendant une analyse de l'usage du temps machine afin
d'éviter d'analyser les méthodes qui n'appartiennent pas à votre application
(par exemple toutes les méthodes java.*).
Ceci permet de garder des performances proches de celles de
l'application non-instrumentée.
-
La fonctionnalité d'analyse de l'usage de la mémoire
peut se restreindre au phénomène d'allocation de
nouveaux objets. Là aussi, l'impact en performance du
profileur sera minimisé.
-
Si, en effectuant une analyse de l'usage de la mémoire, vous
constatez que seules quelques classes sont susceptibles d'en
être la cause, vous pouvez supprimer la surveillance de
toutes les autres classes.
-
Il est également possible de mesurer le temps de
démarrage d'une application ou bien seulement un fragment de
code de votre application.
-
Des informations dédiées à chaque thread sont
également disponibles.