web-dev-qa-db-fra.com

Mémoire partagée entre deux machines virtuelles

Existe-t-il un moyen en Java, pour deux JVM (fonctionnant sur la même machine physique), d’utiliser/partager le même espace adresse mermory? Supposons qu'un producteur de JVM1 place les messages dans un emplacement de mémoire prédéfini particulier. Le consommateur de JVM2 peut-il récupérer le message s'il sait quel emplacement de mémoire consulter?

28
OneWorld

Solution 1:

La meilleure solution à mon avis consiste à utiliser des fichiers mappés en mémoire. Cela vous permet de partager une région de mémoire entre un nombre quelconque de processus, y compris d'autres programmes non Java. Vous ne pouvez pas placer d’objets Java dans un fichier mappé en mémoire, à moins de les sérialiser. L'exemple suivant montre que vous pouvez communiquer entre deux processus différents, mais que vous deviez le rendre beaucoup plus sophistiqué pour permettre une meilleure communication entre les processus. Je vous suggère de regarder le package NIO de Java , en particulier les classes et méthodes utilisées dans les exemples ci-dessous.

Serveur:

public class Server {

    public static void main( String[] args ) throws Throwable {
        File f = new File( FILE_NAME );

        FileChannel channel = FileChannel.open( f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE );

        MappedByteBuffer b = channel.map( MapMode.READ_WRITE, 0, 4096 );
        CharBuffer charBuf = b.asCharBuffer();

        char[] string = "Hello client\0".toCharArray();
        charBuf.put( string );

        System.out.println( "Waiting for client." );
        while( charBuf.get( 0 ) != '\0' );
        System.out.println( "Finished waiting." );
    }
}

Client:

public class Client {

    public static void main( String[] args ) throws Throwable {
        File f = new File( FILE_NAME );
        FileChannel channel = FileChannel.open( f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE );

        MappedByteBuffer b = channel.map( MapMode.READ_WRITE, 0, 4096 );
        CharBuffer charBuf = b.asCharBuffer();

        // Prints 'Hello server'
        char c;
        while( ( c = charBuf.get() ) != 0 ) {
            System.out.print( c );
        }
        System.out.println();

        charBuf.put( 0, '\0' );
    }

}

Solution 2:

Une autre solution consiste à utiliser Java Sockets pour communiquer entre processus. Cela présente l’avantage supplémentaire de permettre une communication très facilement sur un réseau. On pourrait faire valoir que cela est plus lent que d'utiliser des fichiers mappés en mémoire, mais je n'ai pas de points de repère pour sauvegarder cette déclaration. Je ne posterai pas de code pour implémenter cette solution, car il peut s'avérer très compliqué d'implémenter un protocole réseau fiable et est assez spécifique à une application. Il existe de nombreux sites de réseautage intéressants pouvant être trouvés avec des recherches rapides.


Maintenant, les exemples ci-dessus sont si vous voulez partager la mémoire entre deux processus différents. Si vous souhaitez simplement lire/écrire en mémoire arbitraire dans le processus en cours, vous devez tout d'abord savoir certains avertissements. Cela va à l'encontre du principe de la machine virtuelle Java et vous ne devriez vraiment pas le faire dans le code de production. Vous violez toute la sécurité et pouvez très facilement planter la machine virtuelle si vous ne faites pas très attention. 

Cela dit, il est très amusant d’expérimenter. Pour lire/écrire en mémoire arbitraire dans le processus en cours, vous pouvez utiliser la classe Sun.misc.Unsafe. Ceci est fourni sur toutes les machines virtuelles que je connais et que j'ai utilisées. Vous trouverez un exemple d'utilisation de la classe ici .

28
Smith_61

Il existe certaines bibliothèques IPC qui facilitent l'utilisation de la mémoire partagée via des fichiers mappés en mémoire en Java.

Chronicle-Queue

La file d'attente des chroniques est similaire à une Queue Java non bloquante, sauf que vous pouvez proposer un message dans une JVM et l'interroger dans une autre.

Dans les deux machines virtuelles, vous devez créer une instance ChronicleQueue dans le même répertoire FS (localisez ce répertoire dans un FS monté en mémoire si vous n'avez pas besoin de persistance de message):

ChronicleQueue ipc = ChronicleQueueBuilder.single("/dev/shm/queue-ipc").build();

Écrire un message dans une machine virtuelle Java:

ExcerptAppender appender = ipc.acquireAppender();
appender.writeDocument(w -> {
    w.getValueOut().object(message);
});

Lire un message dans une autre machine virtuelle Java:

ExcerptTailer tailer = ipc.createTailer();
// If there is no message, the lambda, passed to the readDocument()
// method is not called.
tailer.readDocument(w -> {
    Message message = w.getValueIn().object(Message.class);
    // process the message here
});

// or avoid using lambdas
try (DocumentContext dc = tailer.readingDocument()) {
    if (dc.isPresent()) {
        Message message = dc.wire().getValueIn().object(Message.class);
        // process the message here
    } else {
        // no message
    }
}

Aeron IPC

Aeron n’est pas seulement une file d’attente IPC (il s’agit d’une infrastructure de communication réseau), mais il offre également une fonctionnalité IPC. Il est similaire à Chronicle Queue, une différence importante est qu’il utilise SBE library pour la gestion des messages/demarshalling, tandis que Chronicle Queue utilise Chronicle Wire .

Carte de la chronique

La carte de la chronique permet IPC de communiquer à l'aide d'une touche. Dans les deux machines virtuelles, vous devez créer une mappe avec des configurations identiques et conservée dans le même fichier (le fichier doit être localisé dans la mémoire montée sur la mémoire FS si vous n'avez pas besoin de la persistance réelle du disque, par exemple dans /dev/shm/):

Map<Key, Message> ipc = ChronicleMap
    .of(Key.class, Message.class)
    .averageKey(...).averageValue(...).entries(...)
    .createPersistedTo(new File("/dev/shm/jvm-ipc.dat"));

Ensuite, dans une machine virtuelle Java, vous pouvez écrire:

ipc.put(key, message); // publish a message

Sur la machine virtuelle destinataire:

Message message = ipc.remove(key);
if (message != null) {
    // process the message here
}
21
leventov

Distributed_cache est la meilleure solution pour répondre à vos besoins.

En informatique, un cache distribué est une extension du concept traditionnel de cache utilisé dans une seule langue. Un cache distribué peut s'étendre sur plusieurs serveurs afin qu'il puisse croître en taille et en capacité transnationale.

Peu d'options:

Terracotta permet aux threads d'un cluster de machines virtuelles Java d'interagir les uns avec les autres, au-delà des limites de la machine virtuelle Java, en utilisant les mêmes fonctions intégrées de la machine virtuelle Java étendues pour avoir une signification globale.

Oracle_Coherence est une grille de données en mémoire basée sur Java proprietary 1 , conçue pour offrir une fiabilité, une évolutivité et des performances supérieures à celles des systèmes de gestion de base de données relationnelle traditionnels.

Ehcache est un cache distribué Java largement utilisé et à code source ouvert destiné à la mise en cache à usage général, à Java EE et aux conteneurs légers. Il comporte des mémoires de mémoire et des magasins de disques, réplique par copie et invalide, écouteurs, chargeurs de cache, extensions de cache, gestionnaires d’exception de cache, filtre de servlet de mise en cache gzip, API RESTful et SOAP.

Redis est un serveur de structure de données. Il est open-source, en réseau, en mémoire et stocke les clés avec une durabilité optionnelle.

Couchbase_Server est un progiciel de base de données NoSQL multimodèle multimodèle à modèles multiples, optimisé pour les applications interactives. Ces applications peuvent desservir de nombreux utilisateurs simultanés en créant, stockant, récupérant, agrégeant, manipulant et présentant des données.

Messages utiles:

Qu'est-ce que la terre cuite?

Terracotta est-il un cache distribué?

infoq article

4
Ravindra babu

Oui,

avec un programme intermédiaire, vous pouvez écrire et lire des emplacements de mémoire arbitraires. Vous ne pouvez pas le faire uniquement en Java.

Par exemple, vous pouvez écrire un morceau de code C++ capable de lire un emplacement mémoire arbitraire et de l'appeler via JNI. La même chose est vraie en sens inverse pour écrire dans une adresse mémoire.

Écrivez d'abord une définition de classe pour la classe qui devrait gérer ceci, par exemple:

public class MemTest {
    public native byte[] readMemory(int address);
    public native void writeMemory(int address, byte[] values);
}

Ensuite, vous le compilez. Ensuite, vous utilisez javah.exe (ou son équivalent linux) pour générer un en-tête:

javah MemTest

Vous écrivez maintenant un fichier .cpp contenant cet en-tête et définissant les méthodes. Compiler en DLL. Pour charger le fichier .dll, vous devez utiliser le paramètre JVM -Djava.library.path avec la valeur appropriée ou System.loadLibrary ().

Note d'avertissement: je ne recommande pas de faire ceci. Il y a presque certainement de meilleures façons de faire ce que vous voulez faire.

2
Xabster

Jocket , un projet expérimental que j'ai réalisé il y a quelques années fait exactement cela.

Il inclut un remplacement instantané pour Java.net.Socket et Java.net.ServerSocket si vous souhaitez utiliser Input/OutputStream.

Chaque canal directionnel utilise une paire de mémoires tampons circulaires pour publier et obtenir des données (une pour les "paquets" et une pour l'adresse des paquets). Les tampons sont obtenus via RandomAccessFile.

Il inclut une petite couche JNI (linux) pour implémenter la synchronisation IPC (c'est-à-dire informer l'autre processus de la disponibilité des données), mais cela n'est pas obligatoire si vous souhaitez interroger des données.

1
pcdv

Unsafe avec la mémoire pivot hors pile

Que diriez-vous d’utiliser Unsafe pour copier des octets d’objet dans une zone distante, puis de passer un pointeur et un nom de classe peu coûteux à la deuxième machine virtuelle Java qui utilisera le pointeur et le nom de la classe pour copier et convertir l’espace vide Objet heap dans 2nd JVM . Ce n'est pas la même instance d'objet mais une copie rapide, sans sérialisation. 

public static Unsafe getUnsafe() {
    try {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        return (Unsafe)f.get(null);
    } catch (Exception e) { /* ... */ }
}

MyStructure structure = new MyStructure(); // create a test object
structure.x = 777;

long size = sizeOf(structure);
long offheapPointer = getUnsafe().allocateMemory(size);
getUnsafe().copyMemory(
            structure,      // source object
            0,              // source offset is zero - copy an entire object
            null,           // destination is specified by absolute address, so destination object is null
            offheapPointer, // destination address
            size
    ); // test object was copied to off-heap

Pointer p = new Pointer(); // Pointer is just a handler that stores address of some object
long pointerOffset = getUnsafe().objectFieldOffset(Pointer.class.getDeclaredField("pointer"));
getUnsafe().putLong(p, pointerOffset, offheapPointer); // set pointer to off-heap copy of the test object

structure.x = 222; // rewrite x value in the original object
System.out.println(  ((MyStructure)p.pointer).x  ); // prints 777

....

class Pointer {
    Object pointer;
}

alors maintenant, vous passez MyStructure et p de ((MyStructure) p.pointer) .x à une 2ème machine virtuelle, et vous devriez pouvoir:

MyStructure locallyImported = (MyStructure)p.pointer;

Je peux imaginer un cas d'utilisation: supposons que vous ayez 2 Microservices qui peuvent ou non fonctionner sur le même serveur, et une stratégie client, éventuellement implémentée dans le conteneur AppServer, qui sait où les services sont déployés, au cas où il détecterait le service demandé En local, il peut utiliser un client de service non sécurisé pour interroger l’autre service de manière transparente. Nasty mais intéressant, je voudrais voir les conséquences sur les performances de ne pas utiliser le réseau, en contournant WebAPI (en appelant directement le contrôleur) et en ne procédant pas à une sérialisation. Outre les paramètres du contrôleur, dans ce cas, le contrôleur lui-même devrait être fourni. Je n'ai même pas pensé à la sécurité.

extraits de code empruntés à https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-Java/

0
juanmf