Optimisation : Utilisation d'un pool d'objets

J'ai lu récemment une série d'articles1 2 3 4 très intéressants ayant pour sujet l'optimisation des programmes Java. La dernière partie parlait de la réutilisation des objets. Un exemple montrait qu'il était plus performant de vider un ArrayList que de créer une nouvelle instance :

for (int i = 0; i < 10; i++) {
    ArrayList list = new ArrayList();
    // Do some stuff...
}
ArrayList list = new ArrayList();
for (int i = 0; i < 10; i++) {
    // Do some stuff...
    list.clear();
}

Dans notre cas, appeler un constructeur est plus coûteux en temps processeur que de simplement supprimer tous les éléments de notre liste (la raison a été discutée sur comp.lang.java.programmer). Le code de droite est donc plus rapide à l'exécution.

J'ai alors eu l'idée de me lancer dans la création d'un pool d'objets. Dans le cas des ArrayList, le but serait de vider notre instance et de la conserver en attendant de la réutiliser plus tard.

J'ai donc testé le code suivant :
public class ArrayListPool<T> {

    private Queue<ArrayList<T>> pool;
    
    public ArrayListPool() {
        pool = new LinkedList<ArrayList<T>>();
    }
    
    public ArrayList<T> getArrayList() {
        synchronized (pool) {
            if (!pool.isEmpty()) {
                return (pool.poll());
            }
        }
        return (new ArrayList<T>());
    }
    
    public void releaseArrayList(ArrayList<T> list) {
        list.clear();
        synchronized (pool) {
            pool.offer(list);
        }
    }
    
    public void clear() {
        synchronized (pool) {
            pool.clear();
        }
    }
}

Le but est d'appeler getArrayList pour obtenir une instance d'un ArrayList, et de la passer en paramètre à la méthode releaseArrayList pour la remettre dans le pool et la réutiliser au besoin.

Concernant les détails de fonctionnement, je me suis servi d'une LinkedList pour stocker les objets à réutiliser. Cette implémentation de Queue est basée sur une liste chaînée, ainsi les insertion et les suppressions d'éléments dans le pool sont très rapides.

Enfin, l'utilisation de ce pool. À gauche, le code de base, qui servira de benchmark par la suite. À droite, le code modifié pour utiliser le pool.
ThreadGroup group2 = new ThreadGroup("Group 2");
for (int i = 0; i < threadCount; i++) {
    Thread t = new Thread(group2, "Thread " + i) {
        @Override
        public void run() {
            for (int i = 0; i < listCount; i++) {
                ArrayList<Object> list = new ArrayList<Object>();
                for (int j = 0; j < objectCount; j++) {
                    list.add(new Object());
                }
            }
        }
    };
    t.start();
}
final ArrayListPool<Object> pool = new ArrayListPool<Object>();
ThreadGroup group1 = new ThreadGroup("Group 1");
for (int i = 0; i < threadCount; i++) {
    Thread t = new Thread(group1, "Thread " + i) {
        @Override
        public void run() {
            for (int i = 0; i < listCount; i++) {
                ArrayList<Object> list = pool.getArrayList();
                for (int j = 0; j < objectCount; j++) {
                    list.add(new Object());
                }
                pool.releaseArrayList(list);
            }
        }
    };
    t.start();
}

Après exécution de ce code en tant que benchmark, voici les résultats :

threadCount listCount objectCount Temps avec pool (ms) Temps sans pool (ms)
100 100 100 65 82
10 100000 100 1961 2051
100 100000 100 17484 24950


Le temps d'exécution est légèrement réduit. En contrepartie, après analyse de l'application de test grâce au Profiler de NetBeans, la consommation mémoire est d'environ 2 Mo supplémentaires pour 100 objets en attente dans le pool. Cette solution est donc intéressante à mettre en place pour des objets dont l'instanciation est très fréquente. Dans le cas où les instanciations se font plus rares, le gain en temps CPU est négligeable, et la consommation mémoire est plus étalée dans le temps.

Permalink  |  Commentaires (2)

GlassFish v3tp2

Je sais que GlassFish v3tp2 est sorti depuis un petit bout de temps, mais je viens juste de le télécharger...

Ma première impression ?

Ça fait vraiment très bizarre :
viv@Eudoxe:~/Programs/glassfish-v3tp2$ time ./bin/asadmin start-domain
Command start-domain executed successfully.

real	0m0.976s
user	0m0.720s
sys	0m0.056s
viv@Eudoxe:~/Programs/glassfish-v3tp2$ time ./bin/asadmin stop-domain
Command stop-domain executed successfully.

real	0m0.553s
user	0m0.640s
sys	0m0.056s

Temps de démarrage de moins de une seconde, temps d'arrêt d'environ une demi seconde...

Sinon, les premières choses qui changent :
  • L'URL de l'interface d'administration. Ça paraît bête, mais j'ai mit bien 5 minutes à trouver la nouvelle (http://localhost:8080/admin)
  • Un déploiement d'applications bien plus rapide qu'avant
  • Le changement de fournisseur de persistance. GlassFish est passé de Toplink à EclipseLink, donc quelques petites modifications sont à faire dans les fichiers de configuration des applications spécifiques.


Le temps de tester un peu plus en profondeur et je pourrais donner un avis un peu plus complet, mais pour l'instant, je suis plutôt enthousiaste :)

Permalink  |  Commentaires (0)

Utiliser un certificat SSL avec GlassFish

Après de nombreuses tentatives, c'est enfin fait ! Le certificat SSL de *.aperigeek.com est valide.

Voici les différentes étapes de mon aventure :
Pour information, mon serveur GlassFish utilise JKS en tant que KeyStore. Il s'agit du KeyStore par défaut sur une installation basique de GlassFish.

Générer une paire de clés

Le JDK embarque un outil permettant de gérer les KeyStore de type JKS. Cet outil porte le doux nom de keytool.

La première étape est donc de générer une paire de clés. Ces clés seront utilisées pour chiffrer les données entre le client et le serveur.

Pour générer une paire de clés, la commande magique est :
$ keytool -genkeypair -keyalg <algorithm> -keystore <keystore> -validity <validity> -alias <alias>
Petite note : Pour les utilisateurs du JDK 5, l'option -genkeypair est à remplacer par -genkey.

Les options à fournir :
  • <algorithm> corresponds à l'algorithme utilisé pour générer les clés (RSA, par exemple).
  • <keystore> corresponds au fichier contenant les clés du serveur. Par défault, ce fichier s'appelle keystore.pks et est stocké dans domains/domain/config.
  • <validity> corresponds au temps de validité du certificat, en jour (365 pour un an, par exemple).
  • <alias> corresponds au nom que vous souhaitez donner au certificat. Ce nom sera réutilisé par la suite pour désigner ce certificat à l'intérieur du serveur d'application.

keytool vous posera plusieurs questions, notamment :
  • Le mot de passe du KeyStore (par défaut, le mot de passe du KeyStore est changeit).
  • Un CN (Common Name). Mettez le nom de domaine pour lequel vous souhaitez créer un certificat (il est possible d'utiliser un wildcard, comme par exemple *.aperigeek.com pour tous les sous domaines aperigeek.com).
  • Les autres informations n'influent pas sur le certificat en lui même.

Générer une demande de certificat (CSR : Certificate Signing Request)

Ce fichier CSR va permettre d'effectuer une demande de certificat auprès d'une autorité de certification. Ce certificat permettra de justifier de l'identité du serveur auprès du client (afin d'éviter certaines attaques de type Man in the Middle, par exemple).

keytool permet de générer cette demande de certificat. La commande est :
keytool -certreq -alias <alias> -file <file> -keystore <keystore>

Avec les options :
  • <alias> corresponds au nom de votre certificat, celui que vous avez choisi dans la première étape.
  • <file> corresponds au fichier qui contiendra votre demande de certificat.
  • <keystore> à votre KeyStore, le même que dans la première étape.

Grâce à cette demande de certificat, nous allons pouvoir maintenant demander un certificat à une autorité de certification.

Demander un certificat à une CA

Libre à vous de choisir une autorité de certification (CA). J'ai choisi CAcert.org, car il s'agit d'une des rares autorité de certifications fournissant des certificats gratuitement.

La demande du certificat dépend ensuite de l'autorité que vous avez choisi. Il se peut que l'autorité de certification vous demande de la rajouter dans en tant que telle dans votre navigateur. Une fois votre certificat créé, vous allez pouvoir le récupérer sois sous la forme d'un fichier .cert, sois sous sa forme textuelle. Dans le deuxième cas, nous allons devoir copier le texte du certificat (y compris les balises -----BEGIN CERTIFICATE----- et -----END CERTIFICATE-----).

Insérer le certificat dans le KeyStore du serveur

Cette étape est assez complexe à réaliser avec keytool, j'ai donc pour ma part utilisé ce code Java :
public class InsertCert {
    public static void main(String[] args) {
        try {
            String certFile = "aperigeek.cert";
            String certName = "*.aperigeek.com";
            String keyStoreFile = "keystore.pks";
            String keyStorePassword = "changeit";
            
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
            Certificate cert = certFactory.generateCertificate(new FileInputStream(certFile));
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());
            Key key = keyStore.getKey(certName, keyStorePassword.toCharArray());
            keyStore.setKeyEntry(certName, key, keyStorePassword.toCharArray(), new Certificate[]{cert});
            keyStore.store(new FileOutputStream(keyStoreFile), keyStorePassword.toCharArray());
        } catch (Exception ex) {
            Logger.getLogger(InsertCert.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
(inspiré de Using SSL with GlassFish v2 : Enterprise Tech Tips)

Les variables à remplacer :
  • certFile : le nom du fichier contenant le certificat, fourni par l'autorité de certification.
  • certName : le nom du certificat, que vous lui avez donné à l'étape 1
  • keyStoreFile : le nom du KeyStore
  • keyStorePassword : le mot de passe du KeyStore

Après exécution de ce morceau de code, voilà votre nouveau certificat entré dans la base de certificats de votre serveur d'applications. Il ne reste plus qu'à configurer GlassFish pour utiliser ce nouveau certificat !

Configuration du service HTTP

Dans la console d'administration de GlassFish :
  • Dans l'arbre : Configuration, Service HTTP, Listeners HTTP, nom-du-listeneur
  • Onglet SSL :
    • Certificate Nickname : nom du certificat (que vous lui avez donné à l'étape 1)
    • Cocher SSL3 et TLS
  • Onglet HTTP Listener :
    • Cocher Security Enabled
  • Redémarrer GlassFish


Et voilà le résultat. Vous pouvez maintenant consulter mon blog en HTTPS avec un certificat valide, si le cœur vous en dit.

Permalink  |  Commentaires (1)

Exécuter du JavaScript en Java

Suite à un sujet sur le forum comp.lang.java.programmer, j'ai fait quelque recherches sur l'exécution de scripts (en particulier JavaScript) dans un environnement Java SE.

L'exécution de scripts dans un environnement Java est fourni par le package javax.script (depuis Java 6). Un moteur de script est défini par l'interface ScriptEngine, dont l'implémentation nous est fournie par la classe ScriptEngineManager. Voici donc à quoi ressemble l'exécution de code JavaScript dans un environnement Java.

1. Exécution simple de script

Dans un premier temps, cet exemple montre comment exécuter un script simple. Le but est d'exécuter le code JS suivant :
function myFunction() {
    return (4 + 5);
}
myFunction();

Le but de ce code est de déclarer une méthode qui renvoie le résultat de 4 + 5, puis d'appeler cette méthode.

Voici maintenant l'exécution du même code en Java, en affichant le résultat sur la sortie standard :
public static void main(String[] args) {
    try {
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("JavaScript");
        String myJSCode = new StringBuffer()
                .append("function myFunction() {")
                .append("return (4 + 5);")
                .append("}")
                .append("myFunction();").toString();
        System.out.println(engine.eval(myJSCode));
    } catch (ScriptException ex) {
        ex.printStackTrace();
    }
}

Il est également possible de séparer la déclaration de la méthode de l'appel, ce qui permet plus de modularité (par exemple, nous déclarons une méthode une seule fois pour l'appeler plusieurs fois) :
public static void main(String[] args) {
    try {
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("JavaScript");
        String myJSCode = new StringBuffer()
                .append("var i = 12;")
                .append("function myFunction() {")
                .append("return (i++);")
                .append("}").toString();
        engine.eval(myJSCode);
        System.out.println(engine.eval("myFunction();"));
        System.out.println(engine.eval("myFunction();"));
        System.out.println(engine.eval("myFunction();"));
    } catch (ScriptException ex) {
        ex.printStackTrace();
    }
}

L'exécution de script est donc très simple.

2. Partage de variables

En plus d'être simple, l'exécution de scripts est également très puissante. Il est possible de partager des références vers des objets entre notre code Java et notre code JavaScript.

Un petit exemple simple, mettant en scène un StringBuffer dans lequel nous allons rajouter des données en JavaScript :
public static void main(String[] args) {
    try {
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("JavaScript");
        StringBuffer s = new StringBuffer();
        engine.put("s", s);
        engine.eval("s.append('toto');");
        System.out.println(s.toString());
    } catch (ScriptException ex) {
        ex.printStackTrace();
    }
}

Il est donc possible de modifier des objets Java directement à l'intérieur de notre moteur de script.

Enfin, un dernier point concerne les objets immuables (comme la classe String, par exemple). En effet, une instance de String ne peut pas changer de contenu. Un nouvel objet doit être créé. Dans ce cas, il est possible de récupérer un objet créé dans notre script :
public static void main(String[] args) {
    try {
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("JavaScript");
        String str = "toto";
        engine.put("str", str);
        engine.eval("str = str.concat(' tata');");
        str = (String) engine.get("str");
        System.out.println(str);
    } catch (ScriptException ex) {
        ex.printStackTrace();
    }
}


L'exécution de scripts est donc assez aisée dans un environnement Java 6. L'API permettant cette exécution est à la fois simple et puissante, puisqu'elle permet même la modification d'objets Java.

Seul bémol, sur ma machine (JVM 1.6.0_06-b02, sous Linux), il semblerais que le moteur de JavaScript soit le seul disponible...

Permalink  |  Commentaires (0)

Nouveau thème pour mon blog

Et oui Keitboor, Matt t'a dénoncé ! Il m'a avoué ce que tu pensais de mon ancien thème. Et je te l'accorde, il n'était pas très esthétique.

Mais maintenant, ce n'est plus le cas !

J'ai décidé d'abandonner Basic, le thème par défaut de Roller, et d'en utiliser un plus frais, unqualified, un des nombreux thèmes proposés par freeCSStemplates.org.

En espérant qu'il vous plaise à vous aussi (vous, mes quelques lecteurs).

Permalink  |  Commentaires (1)