Memoria compartida entre dos JVM

¿Hay alguna forma en JAVA, para dos JVM (que se ejecutan en la misma máquina física), de usar / compartir el mismo espacio de direcciones mermory? Supongamos que un productor en JVM1 coloca mensajes en una ubicación de memoria predefinida, ¿puede el consumidor en JVM2 recuperar el mensaje si sabe qué ubicación de memoria debe mirar?

Solución 1:

La mejor solución en mi opinión es usar archivos mapeados en memoria. Esto le permite compartir una región de memoria entre cualquier cantidad de proceso, incluidos otros progtwigs que no sean Java. No puede colocar objetos Java en un archivo mapeado en memoria, a menos que los serialice. El siguiente ejemplo muestra que puede comunicarse entre dos procesos diferentes, pero deberá hacerlo mucho más sofisticado para permitir una mejor comunicación entre los procesos. Sugiero que consulte el paquete NIO de Java, específicamente las clases y los métodos utilizados en los ejemplos a continuación.

Servidor:

 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." ); } } 

Cliente:

 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' ); } } 

Solución 2:

Otra solución es usar Java Sockets para comunicarse entre procesos. Esto tiene el beneficio adicional de permitir la comunicación a través de una red muy fácilmente. Podría argumentarse que esto es más lento que usar archivos mapeados en memoria, pero no tengo ningún punto de referencia para respaldar esa afirmación. No publicaré el código para implementar esta solución, ya que puede ser muy complicado implementar un protocolo de red confiable y es bastante específico de la aplicación. Hay muchos buenos sitios de redes que se pueden encontrar con búsquedas rápidas.


Ahora los ejemplos anteriores son si desea compartir memoria entre dos procesos diferentes. Si solo desea leer / escribir en la memoria arbitraria en el proceso actual, hay algunas advertencias que debe conocer primero. Esto va en contra de todo el principio de la JVM y realmente no debería hacer esto en el código de producción. Usted viola toda la seguridad y puede bloquear la JVM fácilmente si no tiene mucho cuidado.

Dicho esto, es muy divertido experimentar con. Para leer / escribir en la memoria arbitraria en el proceso actual puede usar la clase sun.misc.Unsafe . Esto se proporciona en todas las JVM que conozco y que he usado. Un ejemplo sobre cómo usar la clase se puede encontrar aquí .

Hay algunas bibliotecas de IPC que facilitan el uso de la memoria compartida a través de archivos mapeados en memoria en Java.

Chronicle-Queue

Cola de crónica es similar a una Queue Java no bloqueante, excepto que podría ofrecer un mensaje en una JVM y sondearla en otra JVM.

En ambas JVM, debe crear una instancia de ChronicleQueue en el mismo directorio de FS (busque este directorio en un FS montado en memoria si no necesita persistencia de mensaje):

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

Escribir un mensaje en una JVM:

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

Lee un mensaje en otra JVM:

 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 es más que una cola de IPC (es un marco de comunicación de red), pero también proporciona una funcionalidad de IPC. Es similar a Chronicle Queue, una diferencia importante es que usa la biblioteca SBE para la clasificación de mensajes / demarshalling, mientras que Chronicle Queue usa Chronicle Wire .

Mapa de la crónica

Chronicle Map permite la comunicación IPC mediante alguna tecla. En ambas JVM, debe crear un mapa con configuraciones idénticas y persistir en el mismo archivo (el archivo debe localizarse en FS montados en memoria si no necesita persistencia real del disco, por ejemplo en /dev/shm/ ):

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

Entonces, en una JVM podrías escribir:

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

En el receptor JVM:

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

Honestamente, no quieres compartir el mismo recuerdo. Debería enviar solo los datos que necesita a la otra JVM. Dicho esto, en el caso de que necesite la memoria compartida, existen otras soluciones.

Envío de datos Dos JVM no comparten los mismos puntos de acceso de memoria, por lo que es imposible utilizar una referencia de una JVM para usar en otra. Una nueva referencia simplemente se creará porque no se conocen entre sí.

Sin embargo, puede enviar los datos a la otra JVM y viceversa de varias formas:

1) Usando RMI , puede configurar un servidor remoto para analizar datos. Me pareció un poco complicado configurarlo porque requiere cambios de seguridad y que los datos sean Serializable . Puede encontrar más información en el enlace.

2) El uso de un servidor es el antiguo método de enviar datos a diferentes lugares. Una forma de implementar esto es usar un ServerSocket y conectarse con un Socket en localhost . Los objetos aún necesitan ser Serializable si desea usar ObjectOutputStream .


Compartir datos Esto es muy peligroso y volátil, de bajo nivel y, bueno, inseguro (literalmente).

Si desea utilizar el código Java, puede echar un vistazo al uso de smUnsafe , utilizando las direcciones de memoria correctas, podrá recuperar Objetos almacenados por las matrices C / C ++ de respaldo en el sistema operativo.

De lo contrario, puede usar métodos native para acceder a las matrices C / C ++, aunque no tengo idea de cómo podría implementarse.

Sí,

con un progtwig intermedio puede escribir y leer ubicaciones de memoria arbitrarias. No puedes hacerlo puramente en Java.

Por ejemplo, puede escribir un fragmento de código C ++ que pueda leer una ubicación de memoria arbitraria y llamarla a través de JNI. Lo mismo es cierto en reversa para escribir en una dirección de memoria.

Escriba una definición de clase primero para la clase que debe manejar esto, por ejemplo:

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

Entonces lo comstack. Luego usa javah.exe (o el equivalente de Linux) para generar un encabezado para él:

 javah MemTest 

Ahora escribe un archivo .cpp que incluye ese encabezado y define los métodos. Comstackr a DLL. Para cargar el .dll, utilice el parámetro -Djava.library.path JVM con el valor apropiado o System.loadLibrary ().

Nota de precaución: no recomiendo hacer esto. Es casi seguro que hay mejores formas de hacer lo que quiere hacer.

Distributed_cache es la mejor solución para satisfacer sus necesidades.

En informática, un caché distribuido es una extensión del concepto tradicional de caché utilizado en una configuración regional única. Un caché distribuido puede abarcar varios servidores para que pueda crecer en tamaño y en capacidad transnacional.

Pocas opciones:

Terracotta permite que los subprocesos en un clúster de JVM interactúen entre sí a través de los límites de JVM utilizando las mismas instalaciones integradas de JVM extendidas para tener un significado de todo el clúster

Oracle_Coherence es una red de datos en memoria basada en Java patentada, diseñada para tener una mejor confiabilidad, escalabilidad y rendimiento que los sistemas de administración de bases de datos relacionales tradicionales

Ehcache es un caché distribuido Java de código abierto ampliamente utilizado para almacenamiento en caché de uso general, Java EE y contenedores livianos. Cuenta con memoria y discos, duplicar e invalidar, oyentes, cargadores de caché, extensiones de caché, manejadores de excepciones de caché, un filtro de servlet gtip caching, API RESTful y SOAP

Redis es un servidor de estructura de datos. Es de código abierto, en red, en la memoria y almacena claves con durabilidad opcional.

Couchbase_Server es un paquete de software de base de datos orientado a documentos distribuido (compartido-nada arquitectónico) multimodo de fuente abierta, optimizado para aplicaciones interactivas. Estas aplicaciones pueden servir a muchos usuarios concurrentes al crear, almacenar, recuperar, agregar, manipular y presentar datos.

Publicaciones útiles:

¿Qué es Terracota?

¿Es Terracotta un caché distribuido?

artículo de infoq

Jocket , un proyecto experimental que hice hace unos años, hace exactamente esto.

Incluye un reemplazo java.net.Socket para java.net.Socket y java.net.ServerSocket si desea utilizar Input/OutputStream .

Cada canal direccional utiliza un par de búferes circulares para publicar y obtener datos (uno para los “paquetes” y otro para la dirección de los paquetes). Los almacenamientos intermedios se obtienen a través de un RandomAccessFile .

Incluye una pequeña capa JNI (linux) para implementar la sincronización IPC (es decir, notificar al otro proceso de disponibilidad de datos) pero esto no es obligatorio si desea sondear datos.

Inseguro con la memoria pivote fuera de montón

¿Qué pasa con el uso de Inseguro para copiar bytes de objetos a una zona fuera de línea, y luego cómo se pasa un puntero y nombre de clase baratos a la 2ª JVM que usará el puntero y nombre de clase para copiar y convertir el espacio fuera de stack en un objeto de montón en 2ª JVM. No es la misma instancia de objeto sino una copia rápida, sin serialización.

 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; } 

así que ahora pasa MyStructure p desde ((MyStructure) p.pointer) .x a una segunda JVM, y usted debería ser capaz de:

 MyStructure locallyImported = (MyStructure)p.pointer; 

Puedo imaginar un caso de uso: supongamos que tiene 2 microservicios que pueden o no ejecutarse en el mismo servidor, y una estrategia de cliente, tal vez implementada en el contenedor AppServer, que sabe dónde se implementan los servicios, en caso de que detecte el servicio solicitado está en local, podría usar un cliente de servicio inseguro para consultar el otro servicio de forma transparente. Desagradable pero interesante, me gustaría ver las implicaciones de rendimiento de no usar la red, pasando por alto WebAPI (llamando directamente al controlador) y no serializando. Además de los parámetros del controlador, en este caso debería proporcionarse el controlador. Ni siquiera pensé en la seguridad.

fragmentos de código tomados de https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/