两个 JVM 之间的共享内存

新手上路,请多包涵

Java 中有没有办法让两个 JVM(在同一台物理机器上运行)使用/共享相同的内存地址空间?假设 JVM-1 中的生产者将消息放在特定的预定义内存位置,如果 JVM-2 上的消费者知道要查看哪个内存位置,它是否可以检索消息?

原文由 OneWorld 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 2k
2 个回答

解决方案 1:

我认为最好的解决方案是使用内存映射文件。这允许您在任意数量的进程(包括其他非 Java 程序)之间共享内存区域。除非序列化它们,否则不能将 java 对象放入内存映射文件中。以下示例显示您可以在两个不同的进程之间进行通信,但是您需要使其更加复杂以允许进程之间更好的通信。我建议你看看 Java 的 NIO 包,特别是下面示例中使用的类和方法。

服务器:

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

客户:

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

}


解决方案 2:

另一种解决方案是使用 Java Sockets 在进程之间来回通信。这具有允许非常容易地通过网络进行通信的额外好处。可以说这比使用内存映射文件慢,但我没有任何基准来支持该声明。我不会发布实现此解决方案的代码,因为实现可靠的网络协议会变得非常复杂,并且相当特定于应用程序。可以通过快速搜索找到许多不错的社交网站。


现在上面的例子是如果你想在两个不同的进程之间共享内存。如果您只想在当前进程中读取/写入任意内存,您应该首先了解一些警告。这违背了 JVM 的整个原则,你真的不应该在生产代码中这样做。您违反了所有安全规定,如果您不小心,很容易使 JVM 崩溃。

话虽如此,尝试它还是很有趣的。要读取/写入当前进程中的任意内存,您可以使用 sun.misc.Unsafe 类。这在我知道并使用过的所有 JVM 上都提供。可以在 此处 找到有关如何使用该类的示例。

原文由 Smith_61 发布,翻译遵循 CC BY-SA 3.0 许可协议

有一些 IPC 库可以通过 Java 中的内存映射文件促进共享内存的使用。

编年史队列

Chronicle Queue 类似于非阻塞 Java Queue ,除了你可以在一个 JVM 中提供一条消息并在另一个 JVM 中轮询它。

在这两个 JVM 中,您应该在同一 FS 目录中创建一个 ChronicleQueue 实例(如果不需要消息持久性,请将此目录放置在内存挂载的 FS 中):

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

在一个 JVM 中写一条消息:

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

在另一个 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 队列(它是一个网络通信框架),它还提供了 IPC 功能。它类似于 Chronicle Queue,一个重要区别是它使用 SBE 库进行消息编组/解组,而 Chronicle Queue 使用 Chronicle Wire

编年史地图

Chronicle Map 允许通过一些密钥进行 IPC 通信。在这两个 JVM 中,您应该创建一个具有相同配置的映射并持久保存到同一个文件(如果您不需要实际的磁盘持久性,该文件应该位于内存挂载的 FS 中,例如 /dev/shm/ ):

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

然后在一个 JVM 中你可以这样写:

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

在接收者 JVM 上:

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

原文由 leventov 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏