OneWorld
OneWorld

Reputation: 992

Shared Memory between two JVMs

Is there a way in Java, for two JVMs (running on same physical machine), to use/share the same memory address space? Suppose a producer in JVM-1 puts messages at a particular pre-defined memory location, can the consumer on JVM-2 retrieve the message if it knows which memory location to look at?

Upvotes: 51

Views: 63183

Answers (7)

Ravindra babu
Ravindra babu

Reputation: 38950

Distributed_cache is best solution to address your requirements.

In computing, a distributed cache is an extension of the traditional concept of cache used in a single locale. A distributed cache may span multiple servers so that it can grow in size and in transnational capacity.

Few options:

Terracotta

Oracle_Coherence

Ehcache

Redis

Couchbase_Server

Useful posts:

What is Terracotta?

Is Terracotta a distributed cache?

For my use case with concurrent heavy writes and reads, Terracotta shown best performance compared to other alternatives. But this result may vary based on your business use cases.

  1. How many writes?
  2. How may reads?
  3. Cache Size.

Upvotes: 8

Xabster
Xabster

Reputation: 3720

Yes,

with an intermediate program you can write to and read arbitrary memory locations. You cannot do it purely in Java.

For example you can write a piece of C++ code that can read an arbitrary memory location and call that via JNI. The same is true in reverse to write to a memory address.

Write a class definition first for the class that should handle this, for example:

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

Then you compile it. Then you use javah.exe (or linux equivalent) to generate a header for it:

javah MemTest

Now you write a .cpp file that includes that header and defines the methods. Compile to DLL. To load the .dll you either use the -Djava.library.path JVM parameter with appropriate value, or System.loadLibrary().

Note of caution: I do not recommend doing this. There are almost certainly better ways to do what you want to do.

Upvotes: 1

pcdv
pcdv

Reputation: 91

Jocket, an experimental project I made a few years ago does exactly this.

It includes a drop-in replacement for java.net.Socket and java.net.ServerSocket if you want to use Input/OutputStream.

Each directional channel uses a pair of circular buffers to post and get data (one for the "packets" and one for the address of packets). The buffers are obtained through a RandomAccessFile.

It includes a small JNI layer (linux) to implement IPC synchronization (i.e. notify the other process of availability of data) but this is not mandatory if you want to poll for data.

Upvotes: 2

juanmf
juanmf

Reputation: 2004

Unsafe with pivot off-heap memory

What about using Unsafe to copy Object bytes to an off-head zone, then some how pass a cheap pointer and class name to the 2nd JVM that will use the pointer and class name to copy and cast the off-heap space to an in-heap object in 2nd JVM. Its not the same object instance but a fast copy, without serializing.

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

so now you pass MyStructure and p from ((MyStructure)p.pointer).x to a 2nd JVM, and you should be able to:

MyStructure locallyImported = (MyStructure)p.pointer;

I can imagine a use case: suppose you have 2 Microservices that may or may not be running in same server, and a client strategy, maybe implemented in the container AppServer, that knows where the services are deployed, in case it detects the requested service is in local, it might use an Unsafe based service client to query the other service transparently. Nasty but interesting, I'd like to see performance implications of not using network, bypassing WebAPI (calling directly handling controller) and not serializing. Apart from the controller parameters in this case the controler itself should be provided. Didn't even think about Security.

code snippets borrowed from https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/

Upvotes: -1

leventov
leventov

Reputation: 15313

There are some IPC libraries which facilitate use of shared memory via memory-mapped files in Java.

Chronicle-Queue

Chronicle Queue is similar to a non-blocking Java Queue, except you could offer a message in one JVM and poll it in another JVM.

In both JVMs you should create a ChronicleQueue instance in the same FS directory (locate this directory in a memory-mounted FS if you don't need message persistence):

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

Write a message in one JVM:

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

Read a message in another 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 is more than just IPC queue (it is a network communication framework), but it provides an IPC functionality as well. It is similar to Chronicle Queue, one important difference is that it uses SBE library for message marshalling/demarshalling, while Chronicle Queue uses Chronicle Wire.

Chronicle Map

Chronicle Map allows IPC communication by some key. In both JVMs, you should create a map with identical configurations and persisted to the same file (the file should be localed in memory-mounted FS if you don't need actual disk persistence, e. g. in /dev/shm/):

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

Then in one JVM you could write:

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

On the reciever JVM:

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

Upvotes: 32

user2982130
user2982130

Reputation:

Honestly, you don't want to share the same memory. You should send only the data that you need to the other JVM. That being said, in the case you do need the shared memory, other solutions exist.

Sending Data Two JVMs do not share the same memory access points, so it is impossible to use a reference from one JVM to use in another. A new reference will simply be create because they don't know about each other.

However, you may ship the data to the other JVM, and back in a variety of ways:

1) Using RMI, you can setup a remote server to parse data. I found it a bit of a hassle to set up because it requires security changes and that the data be Serializable. You can find out more at the link.

2) Using a server is the age-old method of sending data to different places. One way to implement this is using a ServerSocket and connecting with a Socket on localhost. Objects still need to be Serializable if you want to use ObjectOutputStream.


Sharing Data This is very dangerous and volatile, low-level, and, well, unsafe (literally).

If you want to use Java code, you can take a look at using s.m.Unsafe, using the correct memory addresses, you will be able to retrieve Objects stored by the backing C/C++ arrays in the OS.

Otherwise, you can use native methods to access the C/C++ arrays yourself, although I have no clue how this could be implemented.

Upvotes: 4

Smith_61
Smith_61

Reputation: 2088

Solution 1:

The best solution in my opinion is to use memory mapped files. This allows you to share a region of memory between any number of process, including other non java programs. You can't place java objects into a memory mapped file, unless you serialize them. The following example shows that you can communicate between two different process, but you would need to make it much more sophisticated to allow better communication between the processes. I suggest you look at Java's NIO package, specifically the classes and methods used in the below examples.

Server:

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:

Another solution is to use Java Sockets to communicate back and forth between processes. This has the added benefit of allowing communication over a network very easily. It could be argued that this is slower than using memory mapped files, but I do not have any benchmarks to back that statement up. I won't post code to implementing this solution, as it can become very complicated to implement a reliable network protocol and is fairly application specific. There are many good networking sites that can be found with quick searches.


Now the above examples are if you want to share memory between two different process. If you just want to read/write to arbitrary memory in the current process, there are some warnings you should know first. This goes against the entire principle of the JVM and you really really should not do this in production code. You violate all safety and can very easily crash the JVM if you are not very careful.

That being said, it is quite fun to experiment with. To read/write to arbitrary memory in the current process you can use the sun.misc.Unsafe class. This is provided on all JVMs that I am aware of and have used. An example on how to use the class can be found here.

Upvotes: 40

Related Questions