Le Touilleur ExpressLe Touilleur ExpressLe Touilleur ExpressLe Touilleur Express
  • Accueil
  • A propos de l’auteur
  • A propos du Touilleur Express

Astuces pour identifier la cause d'une java.io.NotSerializable et tuning JBoss

    Home Java Astuces pour identifier la cause d'une java.io.NotSerializable et tuning JBoss

    Astuces pour identifier la cause d'une java.io.NotSerializable et tuning JBoss

    Par Nicolas Martignole | Java | 4 commentaires | 24 janvier, 2008 | 0 | 10 430 affichages
         

    L’exception java.io.NotSerializable se rencontre tard le soir au détour d’une petite période d’inactivité avec votre belle application J2EE… Le serveur essaye de passiver un EJB et boum, voici notre belle exception qui sort du bois. Vous pouvez aussi rencontrer notre amie alors que vous essayez d’envoyer par le réseau un objet java. Nous allons voir comment dompter la bête et aussi facilement identifier ce qui n’est pas sérialisable. Je commence par faire un rappel sur la sérialisation, et ensuite je parlerai de JBoss pour vous montrer comment configurer celui-ci pour identifier les problèmes de passivation avec les EJB.

    Rappel rapide sur la sérialisation en Java: dans le domaine de la programmation, du stockage de données et de la transmission par le réseau, la sérialisation consiste à sauver un objet Java au format binaire. Il est ensuite possible de transmettre cette objet via le réseau, ou de le sauvegarder sur le disque par exemple. Les données binaires respectent un format spécial qui permet à Java de recréer un Objet identique avec le même état que l’original.En anglais, sérialiser un objet se dit « marshalling » et le désérialiser se dit « unmarshalling ».

    Avant tout, certains objets en Java ne sont pas sérialisables: Socket, Thread, OutputStream ou Image par exemple. Cela peut poser un problème à votre application: que se passe-t-il si votre code contient une instance de l’une de ces classes non sérialisable ? Java vous le signale avec l’exception java.io.NotSerializable. Il existe cependant des solutions à ces problèmes.

    La sérialisation par défaut

    Pour persister un objet à l’exécution, nous devons le rendre sérialisable. Java fait appel à un design pattern appelé « Marker ». Ce design pattern consiste à créer une Interface java vide sans méthodes afin de marquer une class Java comme étant capable d’effectuer un certain traitement. J’aime bien demander en entretien d’embauche: « Peut-on avoir une Interface sans méthodes, sans instances ? Si oui, à quoi cela sert-il ? »

    Prenons une class SoccerPlayer qui vient de mes cours sur java:

    import java.util.Date;
    import java.io.Serializable;
    
    /**
     * Created by IntelliJ IDEA.
     * User: nicolasmartignole
     * Date: 22 janv. 2008
     * Time: 23:07:31
     */
    public class SoccerPlayer implements Serializable {
        private String name;
        private Date birthDate;
        private int number;
    
        public SoccerPlayer(String name, Date birthDate, int number) {
            this.name = name;
            this.birthDate = birthDate;
            this.number = number;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Date getBirthDate() {
            return birthDate;
        }
    
        public void setBirthDate(Date birthDate) {
            this.birthDate = birthDate;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    }

    En marquant notre classe avec l’interface java.io.Serializable, celle-ci est prête à être sérialisée. Nous allons écrire un peu de code pour tester en l’état la sérialisation de cette class.

    import java.util.Locale;
    import java.text.DateFormat;
    import java.io.FileOutputStream;
    import java.io.File;
    import java.io.ObjectOutputStream;
    
    /**
     * This class marshalls the SoccerPlayer class to a file.
     * User: nicolasmartignole
     * Date: 22 janv. 2008
     * Time: 23:11:07
     *
     * See online Touilleur Express http://www.touilleur-express.fr
     */
    public class FileSystemStorageEngine {
         private static DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.FRANCE);
    
        /**
         * Creates a new player, performs a serialiation test.
         * @param args is cl args.
         * @throws Exception for any Exception, I don't want to deal with it in my sample.
         */
        public static void main(String[] args) throws Exception{
            SoccerPlayer player = new SoccerPlayer("Nicolas Martignole", df.parse("24/09/1975"), 10);
            FileOutputStream fos=new FileOutputStream(new File("test.ser")) ;
            ObjectOutputStream out=new ObjectOutputStream(fos);
            out.writeObject(player);
    
        }
    }

    La class ObjetOutputStream est la classe qui se charge de sérialiser notre class SoccerPlayer vers le fichier. Si la class SoccerPlayer n’était pas marquée avec l’interface Serializable, Java lève une « java.io.NotSerializable » exception. La class SoccerPlayer est simple, et ses variables d’instance (une String et une Date) peuvent être sérialisées par Java sans problèmes.

    Désérialiser

    La désérialisation s’effectue en utilisant la class ObjectInputStream. Dans l’exemple ci-dessous, je relis le fichier test.ser et je vais afficher le nom du joueur dans la console.

    Nous modifions un peu notre class afin d’appeler une méthode chargée de désérialiser notre fichier. Dans mon exemple je ne fais aucuns traitements d’erreur, et donc si le fichier n’existe pas le programme lévera une FileNotFoundException. Maisi ici le plus important c’est juste de montrer qu’il est simple de recharger un Objet Java en mémoire.

        public static void main(String[] args) throws Exception{
    
            SoccerPlayer loaded=reloadSoccerPlayer();
            System.out.println("Soccer player name: "+loaded.getName());
    
        }
    
         private static SoccerPlayer reloadSoccerPlayer() throws IOException, ClassNotFoundException {
            FileInputStream fis=new FileInputStream(new File("test.ser")) ;
            ObjectInputStream in=new ObjectInputStream(fis);
            SoccerPlayer sp=(SoccerPlayer)in.readObject();
            return sp;
        }

    L’exécution de mon code sur Mac donne

    /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/bin/java -Dfile.encoding=MacRoman -classpath /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/deploy.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/dt.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/javaws.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/jce.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/plugin.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/sa-jdi.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/charsets.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/classes.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/dt.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/jce.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/jconsole.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/jsse.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/laf.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/../Classes/ui.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext/apple_provider.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext/dnsns.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext/localedata.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext/sunjce_provider.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext/sunpkcs11.jar:/Users/nicolasmartignole/IdeaProjects/Test2/out/production/Test2 FileSystemStorageEngine
    
    Soccer player name: Nicolas Martignole
    
    Process finished with exit code 0

    Lorsque la class n’est pas sérialisable

    Nous allons parler de l’exception java.io.NotSerializable. Mais avant tout, je dois continuer à rappeler quelques mécanismes de Java.

    Reprenons notre class SoccerPlayer et modifions-là afin qu’elle ne soit pas sérialisable. Je vais créer un objet « PlayerPosition » qui représente sur le terrain le poste du joueur. Cet objet ne sera pas sérialisable car il est dynamique. Un joueur défenseur peut jouer aillier (enfin je crois) et donc nous imaginons qu’il n’est pas possible de sérialiser cette information.
    Peu importe le code de la class SoccerPosition, nous avons simplement besoin d’avoir une instance de celle-ci dans la class SoccerPlayer. La class SoccerPosition n’implémente pas l’interface java.io.Serializable.

    La class PlayerPosition est très simple:

    public class PlayerPosition {
        String positionName;
    
        public PlayerPosition(String s) {
            positionName =s;
        }
    
        public String getPositionName() {
            return positionName;
        }
    
        public void setPositionName(String positionName) {
            this.positionName = positionName;
        }
    }

    Nous ajoutons une instance dans SoccerPlayer:

    public class SoccerPlayer implements Serializable {
        private String name;
        private Date birthDate;
        private int number;
    
        private PlayerPosition position;
    
        public SoccerPlayer(String name, Date birthDate, int number, PlayerPosition p) {
            this.name = name;
            this.birthDate = birthDate;
            this.number = number;
    	this.position=p;
        }
    

    Je modifie le constructeur de SoccerPlayer afin de passer une instance de PlayerPosition. Voyons maintenant lorsque l’on relance le programme pour sérialiser ce qui se passe:

    public class FileSystemStorageEngine {
         private static DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.FRANCE);
    
        /**
         * Creates a new player, performs a serialiation test.
         * @param args is cl args.
         * @throws Exception for any Exception, I don't want to deal with it in my sample.
         */
        public static void main(String[] args) throws Exception{
            SoccerPlayer player = new SoccerPlayer("Nicolas Martignole",
    					df.parse("24/09/1975"),
    					10,
    					new PlayerPosition("Goal")
    					);
            performSerialization(player);
    
            SoccerPlayer loaded=reloadSoccerPlayer();
            System.out.println("Soccer player name: "+loaded.getName());
    
        }
    
        private static void performSerialization(SoccerPlayer player) throws ParseException, IOException {
            FileOutputStream fos=new FileOutputStream(new File("test.ser")) ;
            ObjectOutputStream out=new ObjectOutputStream(fos);
            out.writeObject(player);
        }
    
        private static SoccerPlayer reloadSoccerPlayer() throws IOException, ClassNotFoundException {
            FileInputStream fis=new FileInputStream(new File("test.ser")) ;
            ObjectInputStream in=new ObjectInputStream(fis);
            SoccerPlayer sp=(SoccerPlayer)in.readObject();
            return sp;
        }
    }

    Avant tout, notez que si vous n’instanciez pas de PlayerPosition pour la class SoccerPlayer, tout va bien se passer. C’est pour cette raison que j’ai mis à jour le constructeur de SoccerPlayer pour forcer l’affectation d’une PlayerPosition.

    Après avoir compilé, le programme plante:

    Exception in thread "main" java.io.NotSerializableException: PlayerPosition
    	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1081)
    	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1375)
    	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1347)
    	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1290)
    	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1079)
    	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
    	at FileSystemStorageEngine.performSerialization(FileSystemStorageEngine.java:34)
    	at FileSystemStorageEngine.main(FileSystemStorageEngine.java:24)

    Ici Java identifie facilement que la class PlayerPosition ne peut pas être sérialisée. Là où souvent vous aller avoir des soucis, c’est avec un serveur d’application comme JBoss ou Weblogic. Le mécanisme de passivation permet à un serveur d’application de libérer de la mémoire en stockant sur le disque des objets sérialisés. Lorsque la passivation échoue, les objets de la session EJB de l’utilisateur ne pourront pas être restaurés. D’où cette exception.

    Avec un serveur J2EE et des EJB 2.1, comment retrouver quelle classe n’est pas sérialisable ?

    Voici l’astuce du jour (et l’objet de ce billet):

    1. Lancer le serveur d’application en mode debug sur une socket
    2. Se connecter via Eclipse ou IntelliJ sur le serveur J2EE en démarrant une session de remote debug avec Java 5.
    3. Charger les écrans dans l’application Web en se connectant
    4. Configurer Eclipse ou IDEA IntelliJ pour que le debugeur s’active lorsque l’exception java.io.NotSerializable est levée (voir plus bas)
    5. Attendre que l’EJB se passive
    6. Votre IDE doit alors se réveiller et vous êtes arretés à l’endroit où la Thread du serveur d’application est entrain de faire la sérialisation. Avec l’inspecteur d’objet d’IntelliJ il est facile d’identifier quelle classe n’est pas serializable

    Sur JBoss la passivation d’un Session Statefull Bean est reglé à 10 minutes, je ne vais pas attendre aussi longtemps

    En effet, il est facile de configurer JBoss afin de passiver tout de suite des Beans. Je vais donner l’explication pour JBoss 4.2.2.

    Ouvrez le fichier $JBOSS_HOME/server/default/conf/standardjboss.xml avec votre éditeur de texte favori (vi). Dans ce fichier de configuration, la section //container-configuration// définit la configuration du containeur. Chaque configuration spécifie des éléments tels que l’invoquer à utiliser, la politique d’interception de jboss, le nombre d’instances de caches et les files d’attente de thread, la configuration du persistence manager et enfin la partie sécurité.
    Nous allons regarder plus en détail la section « container-cache-conf ». Cet élément est passé à l’implémentation de l’InstanceCache si celle-ci supporte l’interface XmlLoadable. Vous pouvez donc implémenter votre propre mécanisme de cache si vous le désirez. Les implémentations actuelles de Joss InstanceCache dérivent de la classe abstraite « org.jboss.ejb.plugins.AbstractInstanceCache ». Cette classe implémente l’interface XmlLoadable et donc sera capable de charger notre configuration.

    Examinons le fichier standardjboss.xml

    
          org.jboss.ejb.plugins.LRUEnterpriseContextCachePolicy
          
              50
              1000000
              300
              600
              400
              60
              1
              0.75
          
    
    

    Deux implémentations possible de CachePolicy sont utilisées dans le fichier standardjboss.xml avec le support d’une configuration que nous allons voir dans un instant
    Les deux implementations sont

    • org.jboss.ejb.plugins.LRUEnterpriseContextCachePolicy
    • org.jboss.ejb.plugins.LRUStatefulContextCachePolicy

    Dans JBoss 4.2, les Entity beans utilisent LRUEnterpriseContextCachePolicy. Les stateful session bean utilisent LRUStatefulContextCachePolicy.
    Voici ce qu’il est possible de configurer:

    • min-capacity nombre d’éléments minimum du cache
    • max-capacity nombre maximum d’éléments du cache
    • overager-period : durée en secondes entre 2 exécutions du système de nettoyage du cache. Lorsque ce système s’active, les beans dont l’âge est supérieur à max-bean-age sont retirés du cache pour être passivé. Vous voyez donc où nous allons en venir.
    • max-bean-age : donne le temps maximum en seconde Durant lequel un bean peut être inactive avant d’être passive.
    • resizer-period: durée en secondes entre 2 exécutions de la thread du “resizer”. Cette tâche se charge de redimensionner le cache en se basant sur les 3 proprietés suivantes. Lorsque cette tâche s’exécute, elle vérifie le temps écoulé entre 2 caches miss et si cette période est inférieure à « min-cache-miss-period » alors le cache est agrandit à la valeur « max-capacity » en utilisant le facteur « cache-load-factor ». Si la période par contre entre 2 miss caches est plus grande que la valeur de “max-cache-miss-period » alors la taille du cache est réduite en utilisant la valeur de « cache-load-factor ». Ce système permet d’avoir un cache de beans adapté à la charge du serveur avec JBoss.

    En plus des paramètres précedents, LRUStatefulContextCachePolicy pour les Session Stateful Bean a les éléments suivants

    • remover-period Spécifie une période en secondes à attendre entre l’exécution de la tâche de nettoyage du cache. La tâche « remover task » se charge de passiver les EJB qui n’ont pas eu d’accès depuis « max-bean-life » secondes. Ce système différent permet de vider les EJB qui ont été passivés afin d’éviter de remplir par exemple le disque. Ce n’est pas le mécanisme de la passivation. C’est un mécanisme de nettoyage des beans sérialisés sur le disque et qui n’ont pas été réactivé depuis « max-bean-life ».
    • max-bean-life Spécifie la période maximale en seconde d’inactivité pour un bean avant que celui-ci ne soit détruit de l’espace de passivation.

    Comment voir si un EJB peut être passivé et restauré facilement avec JBoss ?

    Nous arrivons enfin à l’explication, après avoir parlé sérialisation et JBoss. Editez la section container-cache-conf pour le container « Standard Stateful SessionBean ».
    Vous pouvez limiter le nombre de beans en cache, et donc forcer la passivation pour les beans les plus anciens :
    Ici je déclare que les beans doivent être passivés après 30 secondes (max-bean-age), et qu’ils ont une durée de vie de 1800 secondes(max-bean-life). Cela me laisse du temps pour tester l’activation après passivation. Toutes les 25 secondes je vérifie mon cache afin de voir ce que je dois passiver (overage-period). Toutes les 120 secondes (remover-period) je regarde si je ne peux pas effacer du disque de vieux beans passivés. Je ne touche pas à ce qui est redimensionnement du cache (resizer-period).

    
            org.jboss.ejb.plugins.LRUStatefulContextCachePolicy
            
              10
              30
              25
              30
              400
              60
              1
              0.75
              120
              1800
            
          
    

    Comment désactiver la passivation des EJB avec JBoss ?

    C’est possible, quoique par forcément souhaitable. JBoss fournit une implémentation de CachePolicy appelée org.jboss.ejb.plugins.NoPassivationCachePolicy.
    Cette police ne passivera jamais une instance. Elle est basée sur une Map en mémoire qui ne retire jamais les beans sauf si ceux-ci sont explicitement retirés

    Pour désactiver la passivation :

        
          Standard Stateful SessionBean
            
              org.jboss.ejb.plugins.LRUStatefulContextCachePolicy
    

    J’espère que ce petit guide vous sera utile, n’hésitez pas à me contacter si vous chercher un expert JBoss pour faire du tuning ou si vous avez besoin de conseils pour votre application.

    Articles similaires:

    Default ThumbnailRed Hat rachète JBoss pour 350 millions de dollars Default ThumbnailJBoss RESTeasy Default ThumbnailAtelier JBoss ON the road et JBoss Seam 1.0.0 CR3 Default ThumbnailJBoss Seam et OutOfMemory… mais non
    j2ee, Java, jboss, tuning
    • Avatar
      gik 25 janvier 2008 at 15 h 50 min

      oh … j’ai mal a la tete

    • Avatar
      Manuel 25 janvier 2008 at 16 h 36 min

      Ne pas oublier quand on manipule des données sérializables d’ajouter l’attribut « private static final long serialVersionUID » qui permet de versionner la classe (Cet attribut est sinon généré avec une valeur aléatoire par la JVM au moment du chargement de la classe).

      Ainsi la JVM qui desérialise l’objet vérifiera que la version de la classe qu’elle connait correspond à la version de la classe qu’elle essaie de desérialiser.

    • Avatar
      Jean-Louis Rigau 29 janvier 2008 at 14 h 01 min

      Très bon article.

      En complément, je conseille à tous d’utiliser conjointement la classe NonSerializableAttributeTester qui permet de vérifier la sérialisation de tout object (collections comprises ) passé en attribut de session (HttpSession.setAttribute()).

      Pour plus d’infos :

      http://wiki.jboss.org/wiki/Wiki.jsp?page=DebuggingSerializationErrors

    Recent Posts

    • Podcast à (re)découvrir

      On me demande parfois comment faire sa veille technologique… je suis assez

      20 janvier, 2021
    • Lorsque le réseau social Parler cherche un nouvel endroit pour poser ses encombrantes valises…

      Nous allons parler architecture et plus exactement, du nombre de machines nécessaires

      19 janvier, 2021
    • Veille technologique janvier 2021

      Nouveau format d’article cette semaine : je vous propose de partager mes

      17 janvier, 2021

    Recent Tweets

    • RT  @welovedevs : On lui a demandé s'il pensait que les développeurs étaient considérés dans son entreprise. Voilà ce qu'il a répondu 😩 #Enqu…

      2 hours ago
    • Lunatech a aussi eu l'excellente idée de devenir partenaire avec le projet FP Tower, et cela permettra à tous les e… https://t.co/wMfT5MhviI

      3 hours ago
    • Le site https://t.co/r90vLjZTQt propose un cours d'introduction sur la prog fonctionnelle avec Scala. La qualité de… https://t.co/cwOyzNhL0N

      3 hours ago
    • RT  @JulienTruffaut : I am so happy of this partnership. Lunatech is a fantastic IT consultancy which invests a lot in the training of its em…

      3 hours ago
    •  @k33g_org   @fcamblor   @waxzce   @bluxte  On a un Channel slack pour le hackbreakfast que nous organisions avant les conf… https://t.co/JfTJ2BAMlP

      8 hours ago

    Mots clés

    agile (18) ajax (11) Apple (11) architecture (5) barcamp (5) BarCampJavaParis (5) ddd (5) devoxx (33) esb (6) exo (6) flex (9) geek (5) google (11) grails (5) groovy (10) humeur (12) humour (7) independant (6) iphone (12) Java (76) javascript (7) jazoon (28) jboss (22) jboss seam (12) jsf (9) jug (16) Linux (11) mac (6) mule (5) parisjug (7) paris jug (22) pjug (6) play (8) playframework (6) portlet (5) ria (8) Scala (19) scrum (44) spring (23) Startup (10) twitter (5) usi (21) usi2010 (9) web (16) xebia (7)

    Le Touilleur Express

    Contactez-moi : nicolas@touilleur-express.fr

    Suivez-moi sur Twitter : @nmartignole

    Copyright© 2008 - 2020 Nicolas Martignole | Tous droits réservés
    • A propos de l’auteur
    • A propos du Touilleur Express
    Le Touilleur Express