Services de mobilité et de persistance des applications Java

Transcription

Services de mobilité et de persistance des applications Java
Services de mobilité et de persistance
des applications Java
Sara Bouchenak and Daniel Hagimont
Chapter 6 of Les intergiciels – développemens récents dans CORBA,
Java RMI et les agents mobiles , Edited by Hermès, 2002.
149
Chapitre 6
Services de mobilité et de persistance
des applications Java
6.1. Introduction
Un service de capture/restauration de l’état des flots de contrôle a de nombreux
domaines d’utilisation, tels que la mobilité des applications ou la persistance des
applications. Ce service, seul ou complété par d’autres, peut aider à la mise en œuvre
d’outils comme l’équilibrage dynamique de charge entre plusieurs machines
[NIC 87], la diminution du trafic réseau par déplacement des clients vers leur serveur
[DOU 92], la reconfiguration dynamique d’applications distribuées [BEL 98,
HOF 93], la mise en œuvre de plates-formes à agents mobiles [CHE 95] ou
l’administration de machines [OUE 96]. Il peut aussi servir à la tolérance aux pannes
[OSM 97] et au débogage des applications. La fourniture de ces outils dépend en
partie de la disponibilité d’un service de capture/restauration de l’état des flots de
contrôle.
Le développement d’applications réparties mettant en œuvre un grand nombre de
machines interconnectées par des réseaux généraux, notamment l’Internet, est un
domaine de recherche en plein essor.1Dans ce contexte, le langage à objets Java a
connu un rapide succès. Le compilateur Java fournit un code intermédiaire (byte
code) interprété par une machine virtuelle, la Java Virtual Machine ou JVM. La
JVM est implantée sur la plupart des stations de travail et des ordinateurs personnels,
Chapitre rédigé par Sara BOUCHENAK et Daniel HAGIMONT.
150
Les intergiciels
ce qui fait que le couple langage Java-JVM est considéré comme la plate-forme de
référence des applications développées pour l’Internet. Un des intérêts majeurs de la
très grande diffusion de cette plate-forme est qu’elle permet de considérer
l’ensemble des machines virtuelles Java de l’Internet comme une base homogène
pour construire des services répartis, et notamment des services systèmes.
La machine virtuelle Java fournit plusieurs services facilitant le développement
des applications :
- les services de sérialisation et désérialisation permettent respectivement de
capturer et de restituer l’état d’un graphe d’objets Java ; ces services peuvent
être utilisés pour déplacer des objets entre différentes machines ou pour
sauvegarder des objets sur le disque ;
- le chargement dynamique des classes permet de déplacer du code Java vers
d’autres nœuds.
En revanche, Java ne fournit pas de service permettant de capturer et de restituer
l’état d’un flot de contrôle (ou thread), car la pile Java d’un flot de contrôle n’est pas
accessible. Un tel service permet d’une part d’extraire l’état courant de l’exécution
du flot, et d’autre part de restituer cet état pour que l’exécution reprenne au point où
elle en était au moment de la capture.
Dans ce chapitre, nous décrivons tout d’abord la réalisation d’un service de
capture/restauration d’état des flots Java, un service générique qui peut être utilisé
aussi bien pour la mobilité des applications Java que pour leur persistance. Nous
présentons ensuite les expérimentations effectuées avec ce service et le résultat des
mesures de performances.
La suite de ce chapitre est organisée comme suit : le paragraphe 6.2 décrit l’état
de l’art. Les paragraphes 6.3 et 6.4 présentent respectivement la conception et la
réalisation de notre service de capture/restauration d’état des flots Java. Le
paragraphe 6.5 présente la mise en œuvre de la mobilité et de la persistance des
applications Java grâce à notre service. Quelques expérimentations effectuées avec le
service fourni, une discussion et une évaluation de ce service sont décrites aux
paragraphes 6.6 et 6.7. Enfin, une conclusion est proposée au paragraphe 6.8.
6.2. Etat de l’art
Il existe principalement trois approches pour aborder le problème de capture et
de restauration de l’état des flots Java : une approche dite explicite, une approche
dite implicite se basant sur un préprocesseur de code et une autre approche implicite
qui étend la JVM.
Services de mobilité et de persistance des applications Java
151
La première approche, appelée gestion explicite, consiste à laisser entièrement à
la charge du programmeur de l’application la gestion de la capture et de la
reconstruction de l’état de son application. Le programmeur doit donc ajouter du
code qui effectue ce travail, en des points fixes de l’application, dits points de
sauvegarde. En un point de sauvegarde, le code ajouté doit enregistrer l’état de
l’exécution : il note la dernière instruction exécutée et l’état courant de l’application.
Lors de la reprise de l’exécution, le premier traitement effectué est un branchement
vers le dernier point de sauvegarde enregistré ; ainsi, l’exécution peut reprendre au
point où elle a été interrompue. Cette solution manque de souplesse, car la gestion
des points de sauvegarde est entièrement à la charge du programmeur. De plus,
l’ajout ou la modification de points de sauvegarde induit une modification de
l’application elle-même. Cette gestion explicite est utilisée dans les applications
faisant appel à des plates-formes à agents mobiles [CHE 95] fournissant une
migration faible tels que les Aglets [IBM 96] ou Mole [BAU 98].
Les deux autres approches, dites implicites, fournissent un service transparent de
capture/restauration de l’état des flots de contrôle. Le service fourni est indépendant
du code de l’application ; il se présente sous la forme d’une primitive appelée par
l’application elle-même ou par une application externe. Ces deux approches diffèrent
par leur mise en œuvre :
- la première approche implicite consiste à fournir un préprocesseur qui injecte
du code soit dans le code source de l’application, soit dans son code
intermédiaire Java. Lors de l’exécution de l’application, le code injecté crée
un objet Java, que l’on nommera backup, et l’associe à l’application. Au fur
et à mesure que l’application s’exécute, le code injecté sauvegarde dans
l’objet backup l’état de l’exécution (les appels de méthodes, les valeurs des
variables locales des méthodes). Lorsque l’application désire capturer son
état, il lui suffit d’utiliser l’objet backup que le préprocesseur lui a associé.
Pour restaurer l’état d’une application, les informations sauvegardées dans
l’objet backup sont utilisées pour réinitialiser l’état de l’application : une
réexécution partielle de l’application est effectuée pour reconstruire la pile
d’exécution et réinitialiser les valeurs des variables locales. La principale
motivation de cette approche est qu’elle ne modifie pas la JVM. Mais son
inconvénient est que d’une part, elle induit un surcoût non négligeable sur les
performances de l’application (dû au code injecté par le préprocesseur)
[BOU 00b] et que d’autre part, le coût de l’opération de restauration de l’état
est important en raison de la réexécution partielle de l’application.
Fünfrocken propose une plate-forme à agents mobiles se basant sur un
préprocesseur qui instrumente le code source des applications Java [FUN 98]
et Truyen, ou encore Sakamoto proposent des implantations de mécanismes
de capture d’état de flots de contrôle Java basés sur un préprocesseur qui
instrumente le code intermédiaire de l’application [SAK 00, TRU 00] ;
152
Les intergiciels
- la seconde approche implicite consiste à étendre la machine virtuelle Java
pour en extraire l’état des flots et le rendre accessible par les programmes
Java. Cette extension doit permettre de capturer l’état courant d’un flot et de
le stocker dans un objet Java. L’extension doit également permettre de créer
un nouveau flot et de l’initialiser avec un état préalablement capturé. Un
service de capture/restauration construit en suivant cette approche n’est certes
utilisable que sur des machines virtuelles étendues. Nous avons tout de même
choisi cette approche pour les deux raisons suivantes :
- elle réduit le surcoût induit par le service sur les performances de
l’application (pas de code injecté) et réduit le coût du service de
capture/restauration, car sa mise en œuvre dans la machine virtuelle Java
est principalement native (en C),
- du fait que ce service ait plusieurs domaines d’applications, nous pensons
que c’est un service de base qui doit être intégré à la JVM.
Cette approche a été suivie pour la réalisation de la plate-forme à agents mobiles
Sumatra [RAN 97]. Contrairement à cette plate-forme qui fournit un mécanisme de
mobilité pour ses agents, notre service de capture de flots est plus général : c’est une
sorte de sérialisation/désérialisation de flots de contrôle Java. Ce service peut ainsi
être spécialisé et utilisé pour la mobilité et pour la persistance des applications
[BOU 99].
6.3. Conception du service de capture/restauration d’état des threads Java
Notre service de capture/restauration d’état des flots de contrôle Java a été
intégré à la version Java 2 SDK (ou JDK 1.2) de la machine virtuelle Java
[SUN 00a]. Nous décrivons tout d’abord la structure de l’état d’un flot dans la
machine virtuelle, puis présentons les principes et les choix de conception du service
de capture/restauration d’état.
6.3.1. Etat d’un thread Java
Une machine virtuelle Java peut supporter l’exécution de plusieurs flots de
contrôle ou threads [LIN 96]. L’état d’un thread Java, illustré par la figure 6.1, est
constitué de trois structures de données principales :
- la pile Java. Elle est associée à chaque flot de contrôle de la machine virtuelle
; cette pile décrit l’état d’avancement de l’exécution du flot de contrôle. La
pile Java contient des régions ou frames (figure 6.2) ; une région est créée
lors de l’appel d’une méthode Java puis détruite lors de la terminaison de la
méthode associée. La région contient, entre autres informations, les variables
locales à la méthode et les résultats des calculs intermédiaires ; les valeurs des
Services de mobilité et de persistance des applications Java
153
variables locales ou des résultats intermédiaires peuvent être de différents
types : entier, réel, référence d’objet, etc. De plus, une région contient des
registres tels que le registre de sommet de pile ou le registre compteur ordinal
indiquant la prochaine instruction de code intermédiaire (byte code) à
exécuter (si la méthode associée à la région est une méthode Java et non une
méthode dite « native » dont le code n’est pas interprété) ;
- le tas d’objets. Il y a un tas d’objets par machine virtuelle, partagé par tous les
flots de contrôle de la machine virtuelle. Il regroupe l’ensemble des objets
Java créés et utilisés par les flots de contrôle de la JVM ;
- la zone des méthodes. La machine virtuelle Java possède une zone des
méthodes partagée par l’ensemble des flots de contrôle de cette machine.
Cette zone regroupe l’ensemble des classes Java utilisées par les flots de la
JVM.
Référence
de
classe
Zone de
méthodes
Référence
d’objet
Pile Java
Tas d’objets
Figure 6.1. Etat d’un thread Java
opérande
opérande
Frame
pc
variable n
Pile Java
variable 1
Figure 6.2. Frame Java
L’état d’exécution d’un flot de contrôle Java est donc constitué des trois
ensembles d’informations suivants : sa pile Java, son tas d’objets constitué de
l’ensemble des objets qu’il manipule (objets auxquels la pile Java fait référence) et
sa zone de méthodes constituée des classes qu’il utilise. Les références des classes
utilisées par le flot de contrôle sont également stockées dans sa pile Java.
154
Les intergiciels
6.3.2. Principe du service de capture/restauration
Le service que nous proposons doit fournir deux fonctions : la capture de l’état
courant de l’exécution d’un flot de contrôle Java et la restauration de cet état. Ce
service permet en premier lieu d’interrompre un flot Java en cours d’exécution et
d’extraire son état courant. Cet état peut ensuite être intégré à un nouveau flot Java
qui, une fois lancé, démarre son exécution au point même où le précédent a été
interrompu.
La capture de l’état d’exécution d’un flot Java revient à construire une structure
de données qui contient toutes les informations permettant de restituer cet état. Cette
structure de données doit donc contenir toutes les informations nécessaires à la
reconstruction de la pile Java du flot, de son tas d’objets et de sa zone de méthodes.
Pour extraire l’état d’exécution d’un flot Java, il faut tout d’abord capturer la pile qui
est associée à ce flot. Il faut ensuite identifier l’ensemble des objets et des classes
Java qui sont utilisés par ce flot ; ceci se fait en parcourant la pile Java du flot pour y
récupérer les références vers les objets et les classes Java. Finalement, une structure
de données Java (un objet) est construite, contenant toutes ces informations.
La restauration d’un état d’exécution revient à reconstruire un état d’exécution à
partir de la structure de données obtenue lors d’une opération de capture. Cette
opération de restauration consiste donc à construire un nouveau flot de contrôle Java,
avec une pile Java, un tas d’objets et une zone de méthodes identiques à ceux du flot
ayant subi l’opération de capture.
Après une opération de capture d’état, un objet Java est construit. Cet objet
contient des informations sur la pile, les objets et les classes utilisés par le flot. Pour
construire des services de plus haut niveau, tels que la mobilité ou la persistance des
flots, notre service de base de capture/restauration d’état des flots doit pouvoir se
combiner à d’autres services Java tels que la sérialisation d’objets et le chargement
dynamique des classes. Ainsi :
- la sérialisation sera utilisée pour le transfert des objets,
- le chargement dynamique des classes sera utilisé pour le transfert des classes.
6.3.3. Choix de conception
Deux principaux problèmes ont été rencontrés lors de la conception du service de
capture/restauration de l’état d’exécution des flots de contrôle Java. Le premier est
dû au fait que l’état d’exécution d’un flot n’est pas accessible par les programmes
Java. Le second problème est dû au fait que la pile Java associée à un flot n’est pas
portable sur des architectures hétérogènes.
Services de mobilité et de persistance des applications Java
6.3.3.1.
155
Etat non accessible
L’état d’exécution d’un flot de contrôle est interne à la machine virtuelle Java
standard. Cet état n’est pas accessible par les programmes Java et ne peut donc être
directement capturé. Pour faire face à ce problème, nous avons étendu la machine
virtuelle pour en extraire l’état d’exécution des flots de contrôle et permettre ainsi
leur capture.
6.3.3.2.
Etat non portable
L’état d’exécution d’un flot de contrôle est constitué de trois structures de
données : la pile Java, le tas d’objets et la zone des méthodes. Le tas d’objets et à la
zone des méthodes sont respectivement constitués d’objets Java et de classes Java,
donc de structures de données portables sur des architectures hétérogènes.
Contrairement à ces deux ensembles, la pile Java est une structure de données native
(structure C) ; le codage des informations contenues dans cette pile peut différer
d’une architecture de machine à une autre. La structure de pile Java n’est donc pas
directement portable sur des architectures hétérogènes. Le service de capture d’état
d’un flot doit donc traduire cette structure native en une structure Java qui sera
portable sur différentes architectures.
Traduire la structure native de pile en une structure Java revient plus précisément
à traduire les valeurs natives des variables locales et des résultats intermédiaires
(figure 6.2) en des valeurs Java. Pour traduire une valeur native en une valeur Java, il
faut connaître le type de cette valeur. En effet, un mot de quatre octets peut aussi
bien représenter un entier qu’une référence d’objet. Mais la structure de pile Java ne
donne aucune information sur le type des valeurs qu’elle contient. Le service de
capture d’état d’un flot doit donc pouvoir retrouver le type des valeurs contenues
dans la pile Java associée au flot. Nous proposons deux solutions à ce problème :
une première solution qui consiste à étendre l’interprète Java et une seconde solution
qui consiste à instrumenter le code exécuté par le flot. Nous avons mis en œuvre
chacune de ces deux solutions. Dans ce chapitre, nous n’abordons pas les détails de
réalisation de ces deux techniques, une description plus approfondie pouvant être
trouvée dans [BOU 00a] et [BOU 00b]. L’évaluation présentée au paragraphe 6.7
concerne la technique qui consiste à étendre l’interprète Java.
6.4. Réalisation du service de capture/restauration
Notre service de capture/restauration de l’état des flots Java propose aux
programmeurs Java un nouveau package : le package java.lang.threadpack. Ce
package fournit plusieurs classes telles que la classe ThreadState, dont les instances
représentent l’état d’exécution de flots Java, et la classe ThreadStateManagement
156
Les intergiciels
qui fournit les services de base nécessaires à la capture et à la restauration de l’état
des flots.
La figure 6.3 illustre une partie de l’interface fournie par la classe
ThreadStateManagement. La méthode capture permet de capturer l’état courant d’un
flot Java et de l’affecter à un objet ThreadState qui sera retourné en résultat de la
méthode. Tandis que la méthode restore sert à créer un nouveau flot Java, à
initialiser son état avec l’objet ThreadState passé en paramètre puis à lancer son
exécution, celle-ci reprendra au point où elle a été interrompue au moment de la
capture ; ce nouveau flot sera retourné en résultat de la méthode.
java.lang.threadpack
Class ThreadStateManagement
public final class ThreadStateManagement extends Object
The ThreadStateManagement class provides several useful services for the capture and
restoration of Java thread states.
Method Summary
static ThreadState capture(Thread thread)
Captures the state of the thread argument and returns it as a
ThreadState object.
static Thread restore(ThreadState threadState)
Creates a new Java thread, initializes it with a previously captured state
and starts its execution.
static void captureAndSend(Thread thread, SendInterface sndItf, boolean toStop)
Captures the state of the thread argument and sends it (to a remote node
or to the disk) by calling the sendState method of the SendInterface
interface.
static Thread receiveAndRestore(ReceiveInterface rcvItf)
Receives the state of a Java thread by calling the receiveState method
of the ReceiveInterface interface, creates a new Java thread, initializes it
with the received state and starts its execution.
Figure 6.3. Interface du service de capture/restauration
Les méthodes captureAndSend et receiveAndRestore permettent de spécialiser le
service de capture/restauration en fonction des besoins de l’application qui l’utilise.
En effet, la méthode captureAndSend permet, en plus de la capture de l’état d’un
flot, de spécifier le traitement à effectuer à l’état capturé : celui-ci peut par exemple
être envoyé à une machine distante pour des besoins de mobilité ou écrit sur le
disque dans le cas de la persistance. La spécialisation du traitement à effectuer sur
l’état capturé est possible grâce au paramètre de type SendInterface. En effet, un
objet qui implante notre interface SendInterface fournit une méthode sendState, dans
laquelle sera spécifié le traitement relatif à l’envoi de l’état capturé. Le troisième
paramètre de la méthode captureAndSend permet de spécifier soit l’arrêt du flot dont
l’état a été capturé, soit la reprise de l’exécution de ce flot. Ce paramètre sera par
Services de mobilité et de persistance des applications Java
157
exemple mis à vrai pour de la migration de flot et il sera mis à faux pour du clonage
de flot à distance.
De façon symétrique, la méthode receiveAndRestore permet de spécifier le
traitement à effectuer pour recevoir l’état d’un flot : celui-ci peut par exemple être lu
à partir d’un fichier ou reçu à partir d’une machine distante. La spécialisation de ce
traitement est possible grâce au paramètre de type ReceiveInterface. Un objet qui
implante notre interface ReceiveInterface fournit une méthode receiveState, dans
laquelle sera spécifié le traitement relatif à la réception de l’état à restaurer. Une fois
l’état obtenu, celui-ci sera restauré dans un nouveau flot Java.
6.5. Services de mobilité et de persistance des flots Java
En plus des services de bas niveau fournis par la classe ThreadStateManagement,
nous proposons des services spécifiques à la mobilité des flots Java et des services
spécifiques à la persistance des flots.
La classe MobileThreadManagement appartenant au package threadpack fournit
les services nécessaires à la mobilité des flots de contrôle Java. Une partie de son
interface est illustrée par la figure 6.4. La méthode go permet de transférer
l’exécution d’un flot vers une machine distante identifiée par une adresse IP et un
numéro de port, et la méthode arrive permet de recevoir un nouveau flot à partir
d’une machine dont l’adresse IP et le numéro de port sont passés en arguments de la
méthode.
java.lang.threadpack
Class MobileThreadManagement
public final class MobileThreadManagement extends Object
The MobileThreadManagement class provides several useful services for making Java threads
mobile.
Method Summary
static void go(Thread thread, String targetHost, int targetPort)
Moves the execution of the thread argument to the machine specified by
the host name and the port number arguments.
static Thread arrive(String localHost, int localPort)
Receives a thread on the machine specified by the host name and the
port number arguments.
Figure 6.4. Service de mobilité des threads Java
La figure 6.5 illustre la réalisation des primitives go et arrive. Ces deux
primitives ont été mises en œuvre en faisant appel aux méthodes génériques de la
classe ThreadStateManagement qui sont captureAndSend et receiveAndRestore. Ces
158
Les intergiciels
deux dernières méthodes ont été appelées en utilisant respectivement une instance de
la classe MySender et une instance de la classe MyReceiver. La classe MySender,
décrite par la figure 6.6, implante notre interface SendInterface : elle fournit une
méthode sendState qui transfère l’état du flot vers la machine spécifiée. Et la classe
MyReceiver, décrite également par la figure 6.6, implante notre interface
ReceiveInterface : elle fournit une méthode receiveState qui reçoit l’état du flot de la
machine spécifiée.
public static void go(Thread thread, String targetHost,
int targetPort) {
MySender sndItf = new MySender(targetHost, targetPort) ;
ThreadStateManagement.captureAndSend(thread, sndItf) ;
}
public static Thread arrive(String localHost,
int localPort) {
MyReceiver rcvItf = new MyReceiver(localHost, localPort) ;
return ThreadStateManagement.receiveAndRestore(rcvItf) ;
}
Figure 6.5. Réalisation du service de mobilité
class MySender
class MyReveiver
implements SendInterface {
implements ReceiveInterface
String host ;
{
int port ;
String host ;
int port ;
MySender(String host,
int port) {
MyReceiver(String host,
this.host = host ;
int port) {
this.port = port ;
this.host = host ;
}
this.port = port ;
public void
}
sendState(ThreadState state) { public ThreadState
// Envoi de state à la machine receiveState(
ThreadState state) {
// <host, port>
// Réception de l’état
…
// arrivant sur la machine
}
// <host, port> et retour de
}
// cet état en résultat
…
}
}
Figure 6.6. Réalisation du service de mobilité
Services de mobilité et de persistance des applications Java
159
La méthode sendState établit une connexion vers la machine distante puis envoie
l’objet ThreadState en utilisant la sérialisation Java. L’objet ThreadState fait luimême référence aux objets Java utilisés par le flot ; ces objets feront donc partie du
graphe d’objets sérialisé.
La méthode receiveState accepte une connexion à partir de la machine source
puis reçoit l’objet ThreadState en utilisant la désérialisation Java. Les objets utilisés
par le flot feront également partie du graphe d’objets reçus. Les classes utilisées par
le flot et auxquelles l’objet ThreadState fait référence sont reçues en utilisant le
chargement dynamique des classes. Pour la mise en œuvre de ces deux méthodes, les
services Java standard de sérialisation et de chargement dynamique des classes ont
été utilisés. Mais une spécialisation de ces services permettra d’adapter le transfert
aux besoins de l’application. Un exemple de spécialisation sera présenté au
paragraphe 6.6.2.
De façon similaire, la classe PersistentThreadManagement a été réalisée pour
fournir les services nécessaires à la persistance des flots Java. Une partie de son
interface est illustrée par la figure 6.7. La méthode store permet de sauvegarder l’état
d’exécution d’un flot dans un fichier identifié par un nom donné, et la méthode load
permet de restaurer un flot Java à partir d’un état sauvegardé dans le fichier dont le
nom est passé en argument de la méthode. Ces deux méthodes ont également été
mises en œuvre en adaptant les méthodes génériques captureAndSend et
receiveAndRestore.
Les classes MobileThreadManagement et PersistentThreadManagement sont
donc deux adaptations possibles de notre service générique de capture/restauration
d’état des flots Java. De la même façon et pour des besoins spécifiques à une
application, le programmeur de l’application peut adapter notre service générique et
construire le service répondant au mieux aux attentes de son application.
java.lang.threadpack
Class PersistentThreadManagement
public final class PersistentThreadManagement extends Object
The PersistentThreadManagement class provides several useful services for making Java threads
persistent.
Method Summary
static void store(Thread thread, String fileName)
Saves the state of the thread argument in the file specified by the name
argument.
static Thread load(String fileName)
Restores the execution of a Java thread from the state stored in the file
specified by the name argument.
Figure 6.7. Service de persistance des threads Java
160
Les intergiciels
6.6. Expérimentations
Dans ce paragraphe, nous présentons tout d’abord deux expérimentations
utilisant notre service de mobilité des applications Java. La première
expérimentation sert à mettre en évidence l’utilité de la mobilité forte des flots de
contrôle et donc, l’utilité de notre service de mobilité des flots Java. La seconde
expérimentation montre les limitations de notre service de mobilité et tente d’y
remédier en le combinant à d’autres services Java qui seront adaptés aux besoins de
l’application. Finalement, une discussion décrira certains problèmes rencontrés et les
solutions proposées.
6.6.1. Application récursive mobile : courbe fractale
Nous pouvons considérer deux niveaux de mobilité des applications : la mobilité
dite faible et la celle dite forte. La première ne prend en compte que l’état des
données manipulées par l’application ; l’application mobile verra ainsi ses données
modifiées mais devra, une fois arrivée sur sa nouvelle localisation, reprendre son
exécution depuis le début. En revanche, la mobilité forte prend en compte, en plus de
l’état des données, l’état de l’exécution de l’application (l’état de son flot de
contrôle). Ainsi, une application « fortement mobile » poursuivra son exécution sur
le site destination au point où elle a été interrompue sur le site source.
Choisir la mobilité faible ou la mobilité forte dépend des besoins et des
spécificités de l’application considérée. Prenons par exemple le cas d’une
application Java récursive que l’on veut rendre mobile. Les appels récursifs se
traduisent par une succession de régions (frames) sur la pile associée au flot. La
mobilité faible, qui n’a pas accès à l’état du flot de contrôle, ne permettra pas de
garder une trace des différentes régions (frames) stockées sur la pile : l’exécution,
après migration faible, reprendra depuis le début.
Nous avons considéré le cas d’une application graphique récursive Java : la
courbe fractale du Dragon où l’on voit apparaître un petit dragon à un certain niveau
de récursivité [TRI 93]. Nous avons mis en œuvre cette application et avons utilisé
notre service de mobilité pour déplacer l’application récursive, au cours de son
exécution, d’une machine à une autre. La figure 6.8 illustre cette expérimentation.
L’application lancée sur une première machine est déplacée, au cours de son
exécution, vers une seconde machine où elle poursuit son exécution, puis vers une
troisième machine. Ces ordres de déplacement sont donnés par un flot extérieur au
flot calculant la fractale, en faisant appel à la primitive go de la classe
MobileThreadManagement sur le flot qui calcule la fractale. Cette application
récursive a donc bénéficié de la mobilité forte fournie par notre service et n’a pas eu
à reprendre son exécution sur les machines d’arrivée.
Services de mobilité et de persistance des applications Java
Migration
Migration
Machine 1
161
Machine 2
Machine 3
Figure 6.8. Fractale mobile
6.6.2. Adaptation de la mobilité : Talk
L’objectif de cette seconde expérimentation est de montrer comment notre
service de mobilité peut être combiné à d’autres services Java pour mieux s’adapter
aux besoins de l’application qui l’utilise. Nous considérons ici une application qui se
déplace entre des terminaux aux caractéristiques physiques différentes (ordinateur
personnel, téléphone mobile, etc.).
Notre service de mobilité des flots d’exécution Java est combiné à d’autres
services Java tels que la sérialisation d’objets et le chargement dynamique des
classes :
- la sérialisation est utilisée pour transférer les objets Java entre les deux sites ;
- le chargement dynamique des classes est utilisé pour transférer le code Java
entre les deux sites.
L’adaptabilité peut de ce fait intervenir à différents niveaux : au niveau de la
sérialisation d’objets et au niveau du chargement dynamique des classes.
Considérons l’application de Talk, illustrée par la figure 6.9, où deux utilisateurs
échangent des messages. Chaque utilisateur possède deux canaux de
communication : un canal entrant lui permettant de recevoir les messages de son
interlocuteur, et un canal sortant lui permettant d’envoyer des messages à son
interlocuteur. Les deux utilisateurs lancent initialement leur application de Talk sur
leurs ordinateurs personnels, avec une interface utilisateur graphique. Au cours de
l’exécution de l’application, un des utilisateurs décide de transférer son application
vers une machine aux caractéristiques physiques minimales (un PDA par exemple) et
d’y poursuivre l’exécution.
162
Les intergiciels
Utilisateur 1
Utilisateur 2
Sérialisation
Site 1
Site 2
Migration
Désérialisation
Chargement
dynamique de
classes
Site 3
Figure 6.9. Application de Talk mobile
L’appel à notre service de capture de l’état des flots Java permet de construire un
objet Java contenant, entre autres informations, des renseignements sur les objets et
les classes utilisés par l’application. Deux problèmes restent à résoudre :
- le traitement des canaux de communication reliant les deux utilisateurs ;
- la machine physique minimale ne peut supporter l’exécution d’une interface
utilisateur graphique.
En ce qui concerne les canaux de communication, une spécialisation de la
sérialisation Java avant le transfert permet d’envoyer un message particulier à
l’interlocuteur, le prévenant du prochain déplacement, puis à fermer les connexions.
Une spécialisation de la désérialisation Java permet, après le transfert, de recréer les
canaux de communication et de réinitialiser la connexion.
Pour ce qui est de l’interface utilisateur, le service de chargement dynamique des
classes peut être spécialisé pour utiliser une interface minimale textuelle sur le
nouveau site d’accueil.
Finalement, notre service de mobilité fournit une migration forte aux flots Java,
et les services Java de sérialisation et de chargement des classes peuvent être
spécialisés. La combinaison de ces trois services permet de construire une mobilité
forte, adaptée aux besoins de l’application.
Services de mobilité et de persistance des applications Java
163
6.6.3. Discussion
L’application de Talk mobile, présentée ci-dessus, mettait en évidence certains
problèmes rencontrés lors de la mise en œuvre de la mobilité de l’application. Dans
ce paragraphe, nous développons les principales difficultés liées à la mise en place
de la mobilité et de la persistance des applications. Prenons par exemple le cas de la
mobilité d’un flot :
- que se passe-t-il lorsqu’un flot se déplace d’un site source vers un site
destination alors qu’il accédait à un objet utilisé également par d’autres flots
du site source ?;
- comment sont gérées les communications entre flots de contrôle lorsqu’un des
flots mis en œuvre se déplace d’un site à un autre ?;
- que se passe-t-il lorsqu’un flot est transféré sur un site destination alors qu’il
faisait partie d’une application constituée de plusieurs flots sur le site source ?
Nous abordons, une à une, chacune de ces questions et proposons certaines
solutions.
Que se passe-t-il lorsqu’un flot se déplace d’un site source vers un site
destination alors qu’il accédait à un objet utilisé également par d’autres flots du site
source ? Une première solution consiste à dupliquer l’objet partagé et à en transférer
une copie avec le flot mobile [GAR 86]. Dans ce cas, il faut gérer la cohérence entre
les différentes copies d’un même objet et la synchronisation d’accès à un objet. Une
autre solution au problème de partage d’objets consiste à utiliser un lien de poursuite
entre le site destination et le site source pour que le flot, sur le site destination, puisse
accéder à l’objet sur le site source. Dans ce cas, un problème de disponibilité se pose
lorsqu’une panne survient sur le site source [CHO 83, LU 87].
Comment sont gérées les communications entre flots de contrôle lorsqu’un des
flots mis en œuvre se déplace d’un site à un autre ? Une première solution consiste à
utiliser un lien de poursuite entre le site destination et le site source : les messages
envoyés au flot déplacé transiteront par le site source. Dans ce cas, un problème de
disponibilité en cas de panne du site source se pose. Une autre solution au problème
de communication entre flots consiste à fermer les canaux de communication sur le
site source et à en recréer de nouveaux sur le site destination. Dans ce cas, un
problème d’acheminement des messages en transit sur les anciens canaux se pose,
ainsi qu’un problème de désignation des nouveaux canaux.
Que se passe-t-il lorsqu’un flot est transféré sur un site destination alors qu’il
faisait partie d’une application constituée de plusieurs flots sur le site source ? Le
flot peut être déplacé seul et communiquer avec les autres flots à distance. Il peut
164
Les intergiciels
aussi être déplacé avec l’ensemble des flots ou une partie des flots intervenant dans
l’application.
Finalement, quel que soit le problème abordé, le choix de la solution est
fortement dépendant des besoins de l’application considérée. Nous avons donc
délibérément choisi de fournir un comportement par défaut pour nos services de
mobilité/persistance, un comportement qui considère la machine virtuelle Java
comme une machine centralisée (ce qu’elle est !). Quant aux aspects relatifs à la
distribution et au partage, leur gestion est laissée à la charge du programmeur de
l’application qui optera pour la solution la plus appropriée à son problème.
6.7. Evaluation
Ce paragraphe présente tout d’abord les coûts de base de notre service : le coût
d’une opération de capture d’état de flot Java et celui d’une opération de restauration
d’état. Des coûts de plus haut niveau sont ensuite présentés, tels que celui d’une
migration de flot Java entre deux machines et celui des opérations de sauvegarde et
de reprise d’une exécution à partir d’une image disque.
Les mesures de performances de nos services ont été effectuées sur :
- la version JDK 1.2.2 de la machine virtuelle Java,
- installée sur un processeur Sparc d’une fréquence de 167 Mhz, doté de 64 Mo
de mémoire et d’un système Solaris 2.6,
- sur un réseau Ethernet à 100 Mb/s.
6.7.1. Coûts de base
La durée du traitement effectué par notre service de capture/restauration dépend
de la taille de l’état du flot de contrôle Java lorsque celui-ci fait appel au service. La
taille d’un flot dépend du nombre et de la taille des régions (frames) stockées sur la
pile Java associée à ce flot. Nous ne présentons ici que les résultats des mesures de
performances concernant l’influence du nombre de régions sur les coûts de notre
service de capture/restauration. Pour faire varier le nombre de régions, nous nous
sommes basés sur un calcul récursif (la fonction factorielle).
La figure 6.10 présente la variation du coût d’une opération de capture d’état
d’un flot Java en fonction du nombre de régions contenues dans la pile Java du flot
au moment de la capture. La courbe montre que le coût d’une opération de capture
d’état reste inférieur à 1 ms pour un nombre de régions inférieur à 10. Ce coût atteint
ensuite 2 ms pour 20 régions et 9 ms pour 80 régions.
Services de mobilité et de persistance des applications Java
165
12
Temps (ms)
10
8
6
4
2
0
0
20
40
60
80
100
Nombre de frames
Capture
Figure 6.10. Capture de l’état d’un thread
La figure 6.11 présente la variation du coût d’une opération de restauration d’état
d’un flot Java en fonction du nombre de régions contenues dans la pile Java du flot
au moment de la capture. La courbe montre que le coût d’une opération de
restauration ne varie pas beaucoup. Il est compris entre 0,6 et 0,9 ms pour un nombre
de régions allant jusqu’à 75, puis atteint la milliseconde à partir de 80 régions.
Finalement, les coûts de notre service de base de capture et de restauration sont très
raisonnables, surtout si l’on considère des flots n’ayant pas un nombre important de
régions sur leur pile Java au moment de la capture.
1,2
Temps (ms)
1
0,8
0,6
0,4
0,2
0
0
20
40
60
80
100
Nombre de frames
Restauration
Figure 6.11. Restauration de l’état d’un thread
6.7.2. Coûts de la migration
La courbe en trait plein de la figure 6.12 représente la variation du coût d’une
opération de migration de flot Java en fonction du nombre de régions contenues dans
la pile Java du flot au moment de la migration. Cette figure montre que le coût d’une
opération de migration est autour de 100 ms lorsque le nombre de régions est
inférieur à 5. Ce coût atteint ensuite 185 ms pour 20 régions, 287 ms pour 40 régions
166
Les intergiciels
et 500 ms pour 80 régions. Ce coût peut paraître important, mais il est en fait
principalement dû au coût de transfert de l’état capturé. En effet, rappelons qu’une
migration de flot d’une machine source à une machine destination consiste à capturer
l’état courant du flot, transférer cet état de la machine source vers la machine
destination puis restaurer l’état sur la machine destination.
60 0
Temps (ms)
50 0
40 0
30 0
20 0
10 0
0
0
20
40
60
80
10 0
N o m b re d e fram es
M igra tio n
T ra nsfert é tat
Figure 6.12. Migration de thread
La courbe en pointillés de la figure 6.12 présente le coût du transfert de l’état du
flot entre deux machines. Nous constatons que cette courbe recouvre quasiment la
courbe du coût de la migration. Le coût du transfert de l’état est de 100 ms lorsque le
nombre de régions est inférieur à 5. Ce coût atteint ensuite 180 ms pour 20 régions,
279 ms pour 40 régions et 495 ms pour 80 régions. Le coût du transfert de l’état
représente finalement 98 % du coût total d’une migration de flot Java.
Le temps de transfert est en partie dû au temps de sérialisation du graphe
d’objets. Ce temps de sérialisation peut être amélioré en utilisant l’externalisation
Java [SUN 98]. L’externalisation permet d’écrire sa propre politique de transfert et
de ne considérer que les informations nécessaires à la restitution du graphe d’objets.
Elle peut être jusqu’à 40 % plus rapide que la sérialisation.
6.7.3. Coûts de la persistance
Rendre un flot de contrôle Java persistant revient d’une part à pouvoir
sauvegarder son état sur un support non volatil tel que le disque, et d’autre part à
pouvoir reprendre l’exécution de l’état sauvegardé sur le disque. Nous avons donc
mesuré le coût de la sauvegarde d’un flot Java sur le disque et celui de la reprise de
l’exécution d’un flot à partir d’une image disque.
Services de mobilité et de persistance des applications Java
167
300
Temps (ms)
250
200
150
100
50
0
0
20
40
60
80
100
Nombre de frames
Sauvegarde fichier
Ecriture fichier
Figure 6.13. Sauvegarde de thread sur disque
Sauvegarder un flot sur le disque revient à capturer son état puis à écrire cet état
sur le disque. La courbe en trait plein de la figure 6.13 représente la variation du coût
d’une opération de sauvegarde d’état de flot Java en fonction du nombre de régions
contenues dans la pile Java du flot au moment de la sauvegarde. La courbe en
pointillés de la figure 6.13 représente le coût de l’écriture de l’état sur le disque.
Nous remarquons que les coûts d’une sauvegarde et d’une écriture sur le disque sont
respectivement de 35 ms et 34 ms pour 5 régions, 46 ms et 45 ms pour 10 régions,
71 ms et 70 ms pour 20 régions et 124 ms et 115 ms pour 40 régions. Le coût d’une
sauvegarde de flot sur le disque est principalement dû au coût de l’écriture de l’état
capturé sur le disque : 97 % du temps total de la sauvegarde est passé dans l’écriture
sur le disque. Ce temps d’écriture peut être amélioré d’une part en utilisant
l’externalisation au lieu d’employer la sérialisation, et autre part en effectuant des
écritures asynchrones sur le disque.
350
Temps (ms)
300
250
200
150
100
50
0
0
20
40
60
80
100
Nombre de frames
Reprise d'un fichier
Lecture d'état à partir d'un fichier
Figure 6.14. Reprise d’un thread du disque
De façon symétrique, la courbe en trait plein de la figure 6.14 représente la
variation du coût d’une opération de reprise d’exécution à partir d’une image disque
168
Les intergiciels
en fonction du nombre de régions contenues dans la pile Java du flot au moment de
la capture. La courbe en pointillés représente le temps de lecture de l’état à partir du
disque. Cette figure montre que le coût d’une opération de reprise est principalement
dû à la lecture de l’état du flot à partir du disque : 99 % du temps total de reprise à
partir d’une image disque est passé dans la lecture disque.
6.7.4. Evaluation de la JVM
En plus de la mesure des performances de nos services, nous nous sommes
intéressés à la mesure de l’éventuel surcoût induit par l’intégration de nos services à
la machine virtuelle Java (JVM). Nous avons, en effet, étendu la JVM pour y
intégrer nos services de capture/restauration de l’état des flots Java. Cette intégration
s’est faite :
- sans modification du langage Java,
- sans modification du compilateur Java,
- sans modification des API Java existantes,
- sans modification de la mise en œuvre des API Java existantes.
Notre extension de la JVM propose de nouvelles API Java et une mise en œuvre
modulaire de ces API. Les nouvelles API fournissent des services de mobilité et de
persistance des flots et des services de capture et de restauration de l’état d’exécution
des flots.
L’objectif de ce paragraphe est de montrer que les performances de notre JVM
étendue restent inchangées par rapport aux performances de la JVM standard.
Autrement dit, nous voulons montrer que l’ajout de nos services à la JVM n’a pas
modifié les performances des autres services de la JVM.
A cet effet, nous avons utilisé un benchmark de machine virtuelle Java appelé
SciMark 2.0 [POZ 00]. SciMark 2.0 est un benchmark de calcul numérique
scientifique Java. Il est constitué de cinq calculs : la transformée de Fourier rapide
(FFT), la résolution de systèmes linéaires par la méthode Jacobi Successive OverRelaxation (SOR), l’intégration Monte Carlo, la factorisation de matrice LU et la
multiplication de matrices (sparse matmut). Ce benchmark présente également un
résultat global (composite score). Tous les résultats sont donnés en Mflops (millions
d’opérations de flottants par seconde). Autrement dit, plus le score résultant d’un
calcul est important, plus la JVM sous-jacente est performante pour ce type de
calcul.
Les calculs effectués par ce benchmark sont de petite taille pour éviter les
problèmes engendrés par le manque d’espace mémoire et se concentrer sur les
Services de mobilité et de persistance des applications Java
169
performances de calcul de la JVM. De plus, le benchmark a été exécuté en
désactivant le compilateur Java JIT pour mesurer les performances réelles de la
JVM.
La figure 6.15 présente les résultats du benchmark SciMark 2.0, dans deux cas :
- le cas de la JVM standard (rectangle blanc),
- le cas de la JVM étendue de nos services (rectangle blanc à points noirs).
Performances
2,0
1,5
1,0
0,5
(1
00
x1
00
)
N
=1
00
0,
...
Ca
r lo
Sp
ar
se
m
at
m
ul
t(
LU
M
on
te
(1
02
4)
R
SO
FF
T
Sc
or
e
e
C
om
po
sit
(1
00
x1
00
)
0,0
Tests
JVM standard
JVM étendue
Figure 6.15. Evaluation de la JVM
Dans ces deux cas, les calculs du benchmark sont exécutés par le thread principal
de la JVM (thread qui exécute la méthode principale main). Les résultats des calculs
montrent que notre JVM étendue présente les mêmes performances que la JVM
standard. L’extension de la JVM avec nos services n’induit donc pas de perte de
performances pour les autres services de la JVM (services utilisés par les calculs du
benchmark). Ce résultat est dû au fait que notre extension de la JVM n’ait pas exigé
la modification de la mise en œuvre des autres services de la JVM.
6.8. Conclusion et perspectives
Un service de capture et de restauration de l’état des flots de contrôle (threads) a
été ajouté à l’environnement Java. Ce service est générique et peut être instancié
pour différents usages. Il a, par exemple, été utilisé comme base pour la mise en
œuvre de services permettant la mobilité et la persistance des flots Java. Par ailleurs,
ces services de mobilité et de persistance des flots peuvent être combinés à d’autres
services Java, tels que la sérialisation d’objets et le chargement dynamique des
classes. Cette combinaison permet de construire des services plus complets et qui
répondent au mieux aux besoins de l’application qui les utilise.
170
Les intergiciels
Des expérimentations qui mettent en évidence d’une part l’utilité de notre service
de mobilité, et d’autre part la construction d’un service de mobilité adaptable, ont été
effectuées et présentées dans ce chapitre. Une évaluation des performances des
services fournis a également été menée et a montré que les coûts obtenus étaient
acceptables.
Nos services peuvent être utilisés comme base pour la mise en œuvre de la
répartition dynamique de charge dans un environnement distribué, pour la mise en
œuvre d’une machine Java persistante [JOR 96] ou d’une machine virtuelle Java
répartie. Ce sont ces utilisations que nous abordons actuellement. Nous nous
intéressons également au portage de nos services sur des machines virtuelles
embarquées pour pouvoir bénéficier de la mobilité entre des machines rudimentaires
telles que les téléphones ou les PDA [SUN 00b]. Notre extension de la machine
virtuelle étant modulaire, le portage de nos services sur une autre implantation de
JVM en est ainsi simplifié.
6.9. Bibliographie
[BAU 98] BAUMANN J., HOHL F., STRABER M., ROTHERMEL K., « Mole - Concepts of
Mobile Agent System », WWW Journal, Special issue on Applications and Techniques of
Web Agents, vol. 1, n. 3, 1998.
http://mole.informatik.uni-stuttgart.de/
[BEL 98]
BELLISSARD L., DE PALMA N, RIVEILL M., « Dynamic Reconfiguration of
Agent-Based Applications », 8th ACM SIGOPS European Workshop, Sintra, Portugal,
septembre 1998,
http://sirac.inrialpes.fr/~riveill
[BOU 99] BOUCHENAK S., HAGIMONT D., « Capture et restauration du contexte
d’exécution d’un thread dans l’environnement Java », 1ère Conférence Française sur les
Systèmes d’Exploitation, Rennes, France, juin 1999.
http://sirac.inrialpes.fr/~bouchena
[BOU 00a] BOUCHENAK S., HAGIMONT D., « Approaches to Capturing Java Thread State »,
Middleware’2000, session de posters, New York, Etats-Unis, avril 2000.
http://sirac.inrialpes.fr/~bouchena
[BOU 00b] BOUCHENAK S., HAGIMONT D., « Pickling threads state in the Java system »,
Proceedings of 33rd International Conference on Technology of Object-Oriented Languages
(TOOLS Europe’2000), Mont-Saint-Michel, France, juin 2000.
http://sirac.inrialpes.fr/~bouchena
[CHE 95]
CHESS D., HARRISON C., KERSHENBAUM A., « Mobile Agents: Are They a Good
Idea? », T.J. Watson Research Center, IBM Research Division, mars 1995.
http://www.research.ibm.com/iagents/ publications.html
Services de mobilité et de persistance des applications Java
171
[CHO 83] CHOU T.C.K., ABRAHAM J. A., « Load Redistribution under Failure in
Distributed Systems », IEEE Transactions on Computers, vol. 32, n. 9, septembre 1983.
[DOU 92] DOUGLIS F., MARSH B., « The Workstation as a Waysttion: Integrating Mobility
into Computing Environment », The Third Workshop on Workstation Operating System
(IEEE), avril 1992.
http://www.douglis.org/fred
[FUN 98]
FÜNFROCKEN S., « Transparent Migration of Java-based Mobile
Agents(Capturing and Reestablishing the State of Java Programs) », Proceedings of Second
International Workshop Mobile Agents 98 (MA’98), Stuttgart, Allemagne, septembre 1998.
http://www.informatik.tu-darmstadt.de/~fuenf
[GAR 86] GARCIA-MOLINA H., « The Future of Data Replication », Symposium on
Reliability in Distributed Software and Database Systems, 1986.
[HOF 93]
HOFMEISTER C., PURTILO J. M., « Dynamic Reconfiguration in Distributed
Systems: Adapting Software Modules for replacement », Proc. of the 13th International
Conference on Distributed Computing Systems, Pittsburgh, Etats-Unis, mai 1993.
[IBM 96]
IBM Tokyo Research Labs, Aglets Workbench: Programming Mobile Agents in
Java, 1996.
http://www.trl.ibm.co.jp/aglets
[JOR 96]
JORDAN M. J., « Early Experiences with Persistent Java », The First
International Workshop on Persistence and Java (PJW’1), Drymen, Ecosse, septembre 1996.
http://www.dcs.gla.ac.uk/pjama
[LIN 96]
1996.
LINDHOLM T., YELLIN F., Java Virtual Machine Specification, Addison Wesley,
[LU 87]
LU C., CHEN A., et LIU J., « Protocols for Reliable Process Migration,
INFOCOM 1987, The 6th Annual Joint Conference of IEEE Computer and Communication
Societies, mars 1987.
[NIC 87]
NICHOLS D. A., « Using Idle Workstations in a Shared Computing
Environment », Proceedings of the Eleventh ACM Symposium on Operating Systems
Principles, novembre 1987.
[OSM 97] OSMAN T., BARGIELA A., « Process Checkpointing in an Open Distributed
Environment », Proceedings of European Simulation Multiconference (ESM’97), juin 1997.
[OUE 96]
OUEICHEK I., Conception et réalisation d’un noyau d’administration pour un
système réparti à objets persistants, Thèse de doctorat, institut national polytechnique de
Grenoble, France, octobre 1996.
[POZ 00]
POZO R., MILLER B., SciMark 2.0 Documentation, 2000.
http://math.nist.gov/scimark2