两个jvm之间的共享内存



在Java中是否有一种方法可以让两个jvm(运行在同一台物理机上)使用/共享相同的内存地址空间?假设JVM-1中的生产者将消息放在特定的预定义内存位置,如果JVM-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".toCharArray();
        charBuf.put( string );
        System.out.println( "Waiting for client." );
        while( charBuf.get( 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, '' );
    }
}

解决方案2:

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


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

话虽这么说,实验起来还是很有趣的。要在当前进程中读写任意内存,可以使用sun.misc.Unsafe类。我所知道和使用过的所有jvm都提供了这个功能。关于如何使用这个类的示例可以在这里找到。

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

Chronicle-Queue

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

Aeron不仅仅是IPC队列(它是一个网络通信框架),它还提供了IPC功能。它与Chronicle Queue类似,一个重要的区别是它使用SBE库进行消息编组/解组,而Chronicle Queue使用Chronicle Wire。

<<h3>编年史地图/h3>

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

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
}

Distributed_cache是满足您需求的最佳解决方案。

在计算中,分布式缓存是在单一语言环境中使用的传统缓存概念的扩展。分布式缓存可以跨越多个服务器,以便它可以在规模和跨国容量上增长。

几个选项:

Terracotta

Oracle_Coherence

Ehcache

复述,

Couchbase_Server

有用的帖子:什么是Terracotta?

Terracotta是分布式缓存吗?

对于我的并发大读写用例,Terracotta比其他替代品表现出最好的性能。但是这个结果可能会根据您的业务用例而有所不同。

  1. 有多少写入?
  2. 读数是多少?
  3. 缓存大小。

老实说,您不想共享相同的内存。您应该只将需要的数据发送到另一个JVM。也就是说,如果确实需要共享内存,则存在其他解决方案。

两个JVM不共享相同的内存访问点,因此不可能使用来自一个JVM的引用在另一个JVM中使用。一个新的引用将被简单地创建,因为它们不知道彼此。

但是,您可以将数据传输到另一个JVM,并以各种方式返回:

1)使用RMI,您可以设置远程服务器来解析数据。我发现设置有点麻烦,因为它需要更改安全性,并且数据是Serializable。你可以在链接上找到更多信息。

2)使用服务器是将数据发送到不同地方的古老方法。实现这一点的一种方法是使用ServerSocket并在localhost上与Socket连接。如果你想使用ObjectOutputStream,对象仍然需要是Serializable


这是非常危险和不稳定的,低级别的,并且,嗯,不安全的(字面上)。

如果你想使用Java代码,你可以看看使用s.m.Unsafe,使用正确的内存地址,你将能够检索由操作系统中支持的C/c++数组存储的对象。

否则,您可以使用native方法来访问C/c++数组自己,虽然我不知道这是如何实现的。

Jocket,我几年前做的一个实验项目就是这样做的。

如果你想使用Input/OutputStream,它包含了java.net.Socketjava.net.ServerSocket的插入式替换。

每个方向通道使用一对圆形缓冲区来发布和获取数据(一个用于"数据包",另一个用于数据包的地址)。缓冲区是通过RandomAccessFile获取的。

它包含一个小的JNI层(linux)来实现IPC同步(即通知其他进程数据的可用性),但如果你想轮询数据,这不是强制性的。

是的,

与中间程序,您可以写入和读取任意内存位置。你不能完全用Java来做。

例如,您可以编写一段c++代码,它可以读取任意内存位置并通过JNI调用它。反过来写内存地址也是如此。

首先为应该处理这个的类编写一个类定义,例如:

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

然后编译它。然后使用java .exe(或linux等效)为它生成一个头文件:

javah MemTest

现在编写一个.cpp文件,其中包含该头文件并定义了方法。编译为DLL。要加载.dll,您可以使用带有适当值的-Djava.library.path JVM参数,或者使用System.loadLibrary()

注意:我不建议这样做。几乎肯定有更好的方法来做你想做的事。

使用枢轴堆外内存不安全

使用不安全将Object字节复制到off-head区域,然后以某种方式将便宜的指针和类名传递给第二个JVM,该JVM将使用指针和类名将堆外空间复制并强制转换为第二个JVM中的堆内对象。它不是同一个对象实例,而是一个快速复制,没有序列化。

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

所以现在你从((MyStructure)p.pointer)传递MyStructurep。到第二个JVM,您应该能够:

MyStructure locallyImported = (MyStructure)p.pointer;

我可以想象一个用例:假设你有两个微服务,可能在同一台服务器上运行,也可能不在同一台服务器上运行,还有一个客户端策略,可能在容器AppServer中实现,它知道服务部署在哪里,如果它检测到请求的服务在本地,它可能使用基于不安全的服务客户端透明地查询其他服务。讨厌但有趣的是,我想看看不使用网络,绕过WebAPI(直接调用处理控制器)和不序列化的性能影响。在这种情况下,除了控制器参数之外,还应该提供控制器本身。根本没想过安全问题。

从https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/借来的代码片段

最新更新