De retour du Paris JUG : ClassLoaders et comparatif de serveurs d’application

Mardi 10 décembre 2013 se tenait la conférence mensuelle gratuite du Java User Group à Paris. Je vous invite à découvrir ou redécouvrir l’association organisatrice et son programme ici : http://www.parisjug.org/xwiki/bin/view/Meeting/20131210.

Le thème de la soirée était : “Les ClassLoaders et JVM Optimisation”. Au premier abord, il peut sembler plutôt vague, surtout si on ne connaît pas le conférencier Simon Maple. Son nom ne vous dit rien ? OK… et si je vous dis qu’il bosse pour ZeroTurnaround ? Toujours rien ? Bon OK… il est évangéliste sur JRebel. Pour les deux du fond qui ne suivent pas, cet éditeur propose une solution qui permet de mettre à jour du code à chaud sans redéployer un jar/war/ear et sans faire un long build maven. En bref, il permet de modifier la signature d’une méthode en vous évitant de perdre dix minutes à redémarrer votre serveur applicatif préféré.

NB. J’aime beaucoup la façon dont leur service marketing s’adresse aux Geeks : JRebel + JAVA = BACON – WRAPPED FERRARI. On comprend aisément que ce produit permet de gagner un temps fou. Cependant, il ne doit pas être simple à coder. Faisons maintenant plus ample connaissance avec un des rouages de la mécanique interne de la JVM.

NB2 : Simon a suggéré à tous de “le suivre sur Twitter car il est payé en fonction du nombre de followers.”

Conférence n°1 : Les ClassLoaders

La première chose que Simon évoque part d’un constat simple : le problème de changement de signature de méthode à chaud vient de la manière dont fonctionne le ClassLoader. Le ClassLoader ne sait pas remplacer ou supprimer (unload) la définition d’une classe ou d’une ressource. Il suffit de regarder l’API Java pour s’en rendre compte. Pour pouvoir ajouter cette fonctionnalité, il faut bien comprendre comment fonctionne le ClassLoader. Celui-ci ne fait que charger ce qu’on lui demande et s’il ne trouve pas, il passe la demande à son père, d’ou la présence d’une méthode getParent. Il n’y a pas de règle dans la norme JEE sur l’ordre de recherche (parent ou enfant d’abord). C’est pourquoi les serveurs d’application permettent de changer ce mécanisme dans leur configuration. Pour ce faire, ils proposent une revue des erreurs liées au ClassLoader que vous avez peut être déjà rencontrée.

NoClassDefFoundError

Pour être exact, voici la stack :

java.lang.NoClassDefFound : Util1
at Test1.doGet
javax.servlet.http.HttpServlet.service…

Cette première stack est assez simple. Ca marche dans l’IDE (Eclipse) car les deux classes sont dans deux jars différents, mais une dépendance est définie (via maven ou directement via la définition du projet) donc ça compile. Cependant, dans le serveur d’application, c’est une autre histoire ! Au cours de l’exécution, la règle est que toute classe inconnue est chargée par le ClassLoader du contexte danslequel elle est utilisée. On charge donc Util1 avec le ClassLoader de Test1. Il suffit de regarder le ClassPath du ClassLoader de Test1 pour se rendre compte qu’il manque le jar contenant Util1. Pour ce faire, on regarde soit dans configuration fichier au niveau de la console d’admin, soit dans la sortie standard lors du déploiement, ou encore en interrogeant le ClassLoader lui-même dans le code :

Response.getWriter().print(Arrays.toString(((URLClassLoader)Test1.class.getClassLoader()).getURLs()));

Si on a un doute sur un jar qui devrait contenir une classe utilisée, la commande suivante permet de le vérifier :

tar -tvf monJar.jar

NoSuchMethodError

Nouvelle tentative avec un bout de code très semblable. Cette fois-ci, la classe Util2 est bien trouvée dans le contexte de Test2 mais on ne trouve pas la méthode qui pourtant est bien définie. Cette erreur est assez classique quand on se retrouve face à des versions différentes de la même classe dans le classPath. Dans ce cas, il faut trouver qui possède la version non désirée de la classe ou de la libraire souhaitée. Simon propose simplement que le ClassLoader nous dise ou il a trouvé la classe en question :

Response.getWriter().print(Test2.class.getClassLoader.getRessource(Util2.class.getName()/replace(‘.’,’/’)+”.class”)));

Il suffit maintenant alors de corriger le classPath pour pointer sur le bon jar ou retirer la référence en trop. On peut aussi vérifier le contenu du jar avec la commande suivante :

jar -tvf monJar.jar

Puis de décompiler la classe qui nous interesse avec javap :

javap -private Utils2.class

Ca  nous permet de connaître la signature de la classe.

SuchNoMethod

Enfin, on rencontre la plus belle des erreurs :

ClassCastException : Util3 cannot be cast to Util3

Pour reprendre Simon : What the F**** ?!

Les classes sont bien définies dans les deux ClassLoaders, mais ici, on utilise une Factory. Cette dernière étant définie dans un autre ClassLoader, on se retrouve avec deux définitions de la même classe et la JVM ne sait pas que c’est la même. En effet, la JVM utilise paramètres pour définir une classe :

  • Nom
  • Package
  • ClassLoader.

Comme son package n’est pas défini (package par défaut), il ne s’agit peut être pas de la même classe étant donné qu’elle provient de deux jars différents. Le fait que la Factory ne renvoie qu’un Object non typé  n’aide pas non plus. On va donc aider la JVM en changeant la signature de la Factory pour qu’elle renvoie le type Util3. Et là…. nouvelle erreur !

IllegalAccessError

Le message d’erreur complet est le suivant : “tried to access method Factory3.instancePackage”. Simon a interrogé la salle en demandant qui avait déjà rencontré cette erreur. Une personne a répondu en disant que oui, mais qu’elle ne l’avait jamais résolue. Ils ont donc quitté JEE pour passer sur du Spring avec un ClassLoader uniquement local. Dommage 🙁

Conclusion

Il y a de nombreuses erreurs liées aux ClassLoaders (les ClassNotFoundException et ClassNoDefFoundException n’ont pas été évoquées ici). Lorsqu’elles apparaissent, il faut demander à l’IDE et au ClassLoader d’où est extraite la définition de la classe qui pose problème, et regarder le contenu des jar et des classes :

  • Rechercher la classe dans l’IDE (control + shift + T dans Eclipse);
  • Chercher les jars contenant la classe : find monRepertoire *.jar -exec jar -tf ‘{}’ ; | grep MaClasse ou encore le site findjar.com pour les classes de jar externe;
  • Regarder le classPatch du contexte : URLClassLoader.getURLs();
  • Lire les logs du serveur applicatif, la partie liée au chargement des jars.

Problème de mémoire

Cette partie est plutôt liée au développement d’un ClassLoader spécifique. Simon Maple nous explique que la moindre fuite dans le ClassLoader peut très vite faire tomber la mémoire. Dans ce cas, c’est toutes les classes chargées par ce ClassLoader qui deviennent inutilisables. L’équipe de Simon a rencontré ce problème lors de la création de JRebel. En effet, on ne peut pas supprimer la définition d’une classe pour ajouter une méthode par exemple. De ce fait, il faut créer un nouveau ClassLoader, copier toutes les classes, sauf celle que l’on veut changer. Pensez si possible à ne pas laisser de lien sur l’ancien ClassLoader car il sera gardé en mémoire, le garbage collector ne supprimant que les objets sans référence pointant dessus.

Le buffet

La plupart des participants se jettent sur le buffet, mais le plus important, c’est de profiter de ce moment pour networker et s’intéresser à ce que font les autres ! Ça m’amuse toujours de voir le décalage entre les sujets abordés lors des conférences qui sont plutôt “hype” et les contextes professionnels dans lesquels évoluent les personnes présentes dans la salle.

“Je fais de la maintenance évolutive sur une appli en JSF 1.1. L’essentiel de mon travail consiste à contourner les bug corrigés ou les fonctionnalité apportés dans les versions suivantes.”

Ce soir-là, je n’ai pas entendu la remarque ci-dessous, mais j’aurais pu :

“On est en train de migrer de Java 1.4 à Java 6.”

Ça fait réfléchir ! Ce doit être un peu  très frustrant. Proposer un sujet sur la manière de faire du neuf avec du vieux ou de faire des trucs cools dans un contexte vieillissant serait intéressant. Récemment, un manager m’a dit ceci :

“L’important n’est pas notre mission actuel, mais de chercher les trucs cool qu’on pourrait y ajouter.”

Je suis tout à fait d’accord avec ça. Cela motiverait d’autant plus les troupes et apporterait une vraie valeur ajoutée à nos clients. Innovons !

Conférence n°2 : JVM Optimisation Comparatif des serveurs d’application

Le titre de la conférence était erroné mais ils suffisait de lire le descriptif pour s’en rendre compte, ce que je n’avais pas fait. L’idée ici était de présenter quelles sont les forces et faiblesses des serveurs applicatifs selon les critères suivants :

  • Installation
  • Documentation
  • Communauté
  • Configuration
  • Temps de démarrage / déploiement / …
  • Etc.

Tout d’abord, Simon a interrogé l’assistance afin de savoir quelle serveur nous utilisions, et si nous en étions contents. Il était étonné de voir que Jetty soit plutôt peu utilisé, et ce avec un niveau de satisfaction assez moyen. Il a présenté rapidement Liberty Profile, le serveur IBM qui se base sur le même conteneur que WebSphere mais qui se veut beaucoup plus léger. Tomcat est sorti grand gagnant de cette phase. Simon nous a ensuite demandé de classer les critères de choix d’un serveur par niveau d’importance (High, Medium, Low). S’ensuit un coup de tableur et… taaadaaa ! Tomcat gagnant.

En bref, on est cohérents. Personnellement, j’ai été moins convaincu par cette partie de la présentation, car il a comparé des serveurs JEE certifiés (JBoss, Websphere, GlassFish, etc.) avec des serveurs légers (Tomcat, Jetty). Il a également comparé les temps de déploiement et d’ouverture de “PetClinic”, l’application de démonstration de Spring Framework. Comment dire…? Déployer une application qui se base sur des beans spring sans utiliser les librairies fournies par le serveur. Ca me semble donc normal d’avoir des temps incomparables car ces outils ne font pas la même chose nativement. Il reste tout de même que la légèreté de tomcat+spring. C’est un peu comme le choix d’utiliser un framework qui fait tout, ou de prendre seulement les librairies qui nous suffisent. Personnellement, mon choix s’oriente vers la simplicité.

SimonMapleParisJUGEnfin, Simon a évoqué TomEE (lire Tommy), qui correspond à un Tomcat gonflé avec l’ajout de librairies pour le rendre conforme la norme J2EE 6 Web Profile. Etant donné que nous avions déjà dépassé le temps qui nous était attribué pour les conférences, Antonio Goncalves nous a proposé de nous retrouver au restaurant le Vavin et pouvoir ainsi poser nos questions au speaker, ou simplement pour échanger autour d’une bière ou d’un repas. Je vous conseille d’y aller au moins une fois ! L’endroit est très sympa pour y refaire le monde autour d’une mousse.

Merci de m’avoir lu jusqu’au bout et rappelez-vous : Innovez !

Photos de la soirée :  Flickr du Paris JUG
Vidéos des présentations : ClassLoader  et Application Server


Share
Guillaume DUFOUR

1140

Leave a Reply

Your email address will not be published. Required fields are marked *