Java Agent – Instrumentez vos classes

Qu’est ce qu’un Agent Java ? Vous les avez sans doute remarqués au détour d’une ligne de commande d’exécution d’un programme Java, pourtant vous ne savez pas trop à quoi ils servent. Comme leur nom vous paraît familier, vous vous êtes certainement contenté de les utiliser sans savoir comment ils fonctionnent. Ils font parti de ces petites options ‘magiques’ qu’il faut utiliser pour que cela marche par ce que « c’est marqué dans la documentation ». C’est donc avec la définition du dictionnaire que nous démarrons ce billet : « Agent : Personne ayant un rôle d’intermédiaire entre deux entités »

Les agents sont arrivés avec le jdk 5. Cela fait donc maintenant quelques années qu’ils existent, et, pourtant, le moins que l’on puisse dire est qu’ils savent se faire discrets : il existe toujours aussi peu de documentation à leur sujet.

Qu’est-ce qu’un agent ?

Les agents permettent d’instrumenter des programmes s’exécutant dasns une JVM en interceptant le chargement des classes et en modifiant directement le bytecode si nécessaire. Un agent est chargé lors du démarrage de la JVM, accompagné ou non d’options. La JVM est belle et bien consciente de son existence, si le chargement d’un agent échoue, l’exécution du programme est interrompue.

java ... -javaagent:jarpath[~ealliaume:=options]  ...

Contrairement à ce que nous pourrions penser, cette option peut-être utilisée autant de fois que nécessaire sur une même ligne de commande. Il est donc possible d’exécuter plusieurs agents en simultané.

Un agent se présente sous la forme d’un JAR devant contenir :

  • des entrées spécifiques dans son Manifest
  • une méthode ‘premain’ servant de point d’entrée pour l’agent, dont voici les signatures possibles :
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);

Notons que les options sont passées sous la forme d’une simple chaîne de caractères : à lui de les parser pour leur donner de la signification.

Lors du démarrage de la JVM, les méthodes ‘premain’ de chacun des agents présents sur la ligne de commande seront appelées avant le démarrage de l’application proprement dite. Bien que leurs noms peuvent nous faire penser à l’exécution d’une application tierce, les agents sont chargés dans le classloader système de la JVM avec les mêmes règles de sécurité que l’application. Aucune restriction particulière n’est donnée sur le code exécuté par les agents : ceux-ci peuvent créer des threads, utiliser l’introspection, … comme bon leur semble.

Le Manifest qui permet de décrire l’agent est composé d’un certain nombre d’attributs. Le seul attribut obligatoire est Premain-Class. Il représente la classe contenant la méthode premain qui sera appelée par l’agent. D’autres attributs viennent compléter la configuration : Boot-Class-Path, Can-Redefine-Classes, Can-Retransform-Classes, nous vous laissons consulter leur description directement sur la javadoc.

Notons qu’il est également possible de démarrer un agent au runtime. Le mécanisme reste à peu près similaire : cette fois-ci le Manifest doit définir l’attribut Agent-Class désignant une classe contenant une méthode agentmain. Nous n’entrerons pas dans les détails ici.

Le Manifest peut évidemment contenir simultanément ces deux arguments Premain-Class et Agent-Class.

Que faire avec un agent ?

Afin de répondre à cette question, étudions des cas réels d’utilisation.

De la programmation orientée objet

AspectJ est une extension Java qui permet de mettre en oeuvre de l’AOP. Il s’agit d’un des frameworks les plus poussés et connus dans son domaine. Pour fonctionner, AspectJ doit modifier des classes pour en produire des nouvelles : le weaving.
Trois types de weaving sont utilisés dans AspectJ :

  • le compilation-time weaving : une classe est générée à la compilation en fonction de mots clés spécifiques
  • le binary weaving (post-compile weaving) : instrumentalisation de classes déjà compilées
  • le load-time weaving : similaire au binary weaving, mais l’instrumentation se fait lors du chargement de la classe.

Comme vous l’aurez deviné, c’est ce dernier point qui nous concerne ici : le load time weaving est rendu possible par l’intermédiaire d’agents. Lors du chargement d’une classe, si AspectJ le juge nécessaire, il va pouvoir modifier le code chargé par son propre code afin d’injecter les aspects.

Du monitoring applicatif

Glassbox est un produit open source qui permet d’effectuer du monitoring applicatif. Il utilise les agents java pour instrumenter des applications existantes de manière non intrusive. Ainsi, l’outil va pouvoir ajouter ses sondes (dont certaines utilisent AspectJ) aux points clés de l’application sans qu’aucune intervention sur le code ne soit nécessaire. Grâce aux agents, GlassBox fournit un ensemble de moniteurs permettant de relever et d’exposer des métriques qui pourront servir à détecter automatiquement différents pics de performances ou problèmes applicatifs. De plus, comme les moniteurs sont chargés au load-time, il est aisé d’en ajouter.

De la persistance avancée

Toplink est l’outil de persistance objet/relationnel d’Oracle. Il s’agit d’une des implémentations JPA au même titre qu’Hibernate. Les agents java sont utilisés par TopLink afin d’optimiser le chargement des relations one-to-one et many-to-one en Lazy Loading (chargement à la demande). Comment cela fonctionne ? Lorsqu’un objet est lié à un autre, deux méthodes de chargement sont possibles : EAGER (directement), LAZY (à la demande). En mode Lazy, lors du chargement d’un objet, vous ne récupérez pas directement les véritables objets liés à celui-ci, mais des proxies sur ceux-ci. Ceux-ci seront chargés lors de leur premier appel de manière transparente. Si vous voulez plus de détails sur ce sujet, et savoir en particulier pourquoi les agents ne sont pas utilisés pour les relations one-to-many, nous vous invitons à consulter cet article.

Du logging …

Pour finir, afin de concrétiser tout cela, nous vous proposons un petit exercice : construire le Manifest et le JAR permettant d’utiliser l’agent suivant.

// la méthode premain
public class MyAgent {
    public static void premain(String agentArgs, Instrumentation inst);
        inst.addTransformer(new PrintingTransformer());
    }
}

// un exemple de tranformer
public class PrintingTransformer implements ClassFileTransformer {
    public byte[] transform(ClassLoader loader, String fullyQualifiedClassName, Class classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classofileBuffer) throws IllegalClassFormatException {
        String className = fullyQualifiedClassName.replaceAll(".*/", "");
        String package = fullyQualifiedClassName.replaceAll("/[a-zA-Z$0-9_]*$", "");
        System.out.printf("Class: %s in: %sn", className, package);
        return null;
    }
}

Vous pouvez télécharger le JAR solution ici.
L’ensemble des sources est disponible dans le repository SVN de Xebia France.

7 commentaires

  • Bonjour,

    je rencontre une erreur lors de l’utilisatation de -javaagent :

    Error occurred during initialization of VM
    Error opening zip file or JAR manifest missing : toplink-essentials-agent.jar
    agent library failed to init: instrument
    Java Result: 1

    avez vous une idée ?

    pour info le jar est bien dans le path
    merci de votre aide

Laisser un commentaire