Java/fr
Java est un langage de programmation de haut niveau orienté objet, créé en 1995 par Sun Microsystems (acquise en 2009 par Oracle). L'objectif central de Java est que les logiciels écrits dans ce langage obéissent au principe write once, run anywhere (écrire une fois, exécuter partout) et soient très facilement portables sur plusieurs systèmes d’exploitation. Le code source Java se compile en code octal (bytecode) qui peut être exécuté sur un environnement Java (JVM pour Java Virtual Machine); différentes architectures et plateformes peuvent donc constituer un environnement uniforme. Cette caractéristique fait de Java un langage populaire dans certains contextes, notamment pour l'apprentissage de la programmation. Même si l'accent n'est pas mis sur la performance, il existe des moyens d'augmenter la vitesse d'exécution, et le langage a connu une certaine popularité auprès des scientifiques dans des domaines tels que les sciences de la vie, d'où sont issus par exemple les outils d'analyse génomique GATK du Broad Institute. L'objectif de cette page n'est pas d'enseigner le langage Java, mais de fournir des conseils et des suggestions pour son utilisation sur les grappes de l'Alliance.
L'Alliance offre plusieurs environnements Java via la commande module. En principe, vous n'aurez qu'un seul module Java chargé à la fois. Les principales commandes associées aux modules Java sont :
* java pour lancer un environnement Java;
* javac pour appeler le compilateur Java qui convertit un fichier source Java en bytecode.
Les logiciels Java sont fréquemment distribués sous forme de fichiers JAR portant le suffixe .jar. Pour utiliser un logiciel Java, utilisez la commande suivante :
Parallélisme¶
Fils d'exécution¶
Java permet la programmation multithread, éliminant ainsi le recours à des interfaces et bibliothèques comme OpenMP, pthreads et Boost qui sont nécessaires avec d'autres langages. L'objet Java principal pour traiter la concurrence est la classe Thread; on peut l'employer en fournissant une méthode Runnable à la classe Thread standard, ou encore en définissant la classe Thread comme sous-classe, comme démontré ici :
public class HelloWorld extends Thread {
public void run() {
System.out.println("Hello World!");
}
public static void main(String args[]) {
(new HelloWorld()).start();
}
}
Cette approche est généralement la plus simple, mais présente cependant le désavantage de ne pas permettre l'héritage multiple; la classe qui implémente l'exécution concurrente ne peut donc pas avoir en sous-classe une autre classe potentiellement plus utile.
MPI¶
On utilise souvent la bibliothèque MPJ Express pour obtenir un parallélisme de type MPI.
Pièges¶
Mémoire¶
Une instance Java s'attend à avoir accès à toute la mémoire physique d'un nœud alors que l'ordonnanceur ou un interpréteur pourrait imposer ses limites (souvent différentes) dépendant des spécifications du script de soumission ou des limites du nœud de connexion. Dans un environnement de ressources partagées, ces limites font en sorte que des ressources à capacités limitées, comme la mémoire et les cœurs de processeur, ne sont pas épuisées par une tâche au détriment d'une autre.
Quand une instance Java est lancée, elle fixe la valeur de deux paramètres selon la quantité de mémoire physique plutôt que la quantité de mémoire disponible, comme suit : * taille initiale du monceau (heap), 1/64 de la mémoire physique; * taille maximale du monceau (heap), 1/4 de la mémoire physique.
En présence d'une grande quantité de mémoire physique, cette valeur de 1/4 peut aisément dépasser les limites imposées par l'ordonnanceur ou par un interpréteur, et Java peut s'arrêter et produire des messages comme :
Could not reserve enough space for object heap
There is insufficient memory for the Java Runtime Environment to continue.
Ces deux paramètres peuvent toutefois être explicitement contrôlés par l'une ou l'autre des options suivantes :
ouPour voir toutes les options en ligne de commande que l'instance exécutera, utilisez l'option -XX:+PrintCommandLineFlags comme suit :
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:ParallelGCThreads=4 -XX:+PrintCommandLineFlags -XX:+UseCompressedOops -XX:+UseParallelGC
Vous pouvez utiliser la variable d'environnement JAVA_TOOL_OPTIONS pour configurer les options d'exécution plutôt que de les spécifier en ligne de commande. Ceci s'avère utile quand des appels multiples sont lancés ou qu'un programme est appelé par un autre programme Java. Voici un exemple :
À l'exécution, le programme émet un message de diagnostic semblable à Picked up JAVA_TOOL_OPTIONS; ceci indique que les options ont été prises en compte.
Conseil sur la mémoire
N'oubliez pas que l'instance Java crée elle-même une réserve d'utilisation de la mémoire. Nous recommandons que la limite par tâche soit fixée à 1 ou 2 Go de plus que la valeur de l'option -Xmx.
Gestion de la mémoire (GC)¶
Java utilise le processus automatique de ramasse-miettes (Garbage Collection ou GC) pour identifier les variables avec des valeurs non valides et retourner la mémoire qui leur est associée au système d'exploitation. Par défaut, l'instance Java utilise un GC parallèle et détermine un nombre de fils de ramasse-miettes égal au nombre de cœurs de processeur du nœud, que la tâche Java soit ou non multithreadée. Chacun des fils de ramasse-miettes consomme de la mémoire. De plus, la quantité de mémoire consommée par les fils de ramasse-miettes est proportionnelle à la quantité de mémoire physique.
Recommandation pour le ramasse-miettes
Nous vous recommandons donc fortement d'avoir un nombre de fils de ramasse-miettes égal au nombre de cœurs de processeur que vous demandez à l'ordonnanceur dans le script de soumission avec par exemple -XX:ParallelGCThreads=12. Vous pouvez aussi utiliser le ramasse-miettes séquentiel avec l'option -XX:+UseSerialGC, que la tâche soit ou non parallèle.
Mot-clé volatile¶
Le sens de ce mot-clé est très différent de celui du même terme utilisé en programmation C/C++. La valeur d'une variable Java ayant cet attribut est toujours lue directement de la mémoire principale et toujours écrite directement dans la mémoire principale; toute modification à la variable sera donc visible par tous les autres fils. Dans certains contextes cependant, volatile ne suffit pas à empêcher les situations de compétition (race conditions), et synchronized est nécessaire pour maintenir la cohérence du programme.
Références¶
OAKS, Scott et Henry Wong, Java Threads: Understanding and Mastering Concurrent Programming, 3e édition, O'Reilly, 2012.