简体   繁体   English

两个 JVM 之间的共享内存

[英]Shared Memory between two JVMs

Is there a way in JAVA, for two JVMs (running on same physical machine), to use/share the same mermory address space? JAVA 中有没有办法让两个 JVM(在同一台物理机器上运行)使用/共享相同的内存地址空间? Suppose a producer in JVM1 puts messages at a particular pre-defined memory location, can the consumer on JVM2 retrive the msg if it knows which memory location to look at?假设 JVM1 中的生产者将消息放在特定的预定义内存位置,如果 JVM2 上的消费者知道要查看哪个内存位置,是否可以检索消息?

Solution 1:解决方案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.这允许您在任意数量的进程之间共享内存区域,包括其他非 Java 程序。 You can't place java objects into a memory mapped file, unless you serialize them.您不能将 java 对象放入内存映射文件中,除非您将它们序列化。 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.我建议您查看 Java 的NIO 包,特别是以下示例中使用的类和方法。

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:解决方案2:

Another solution is to use Java Sockets to communicate back and forth between processes.另一种解决方案是使用 Java Sockets在进程之间来回通信。 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.这违背了 JVM 的整个原则,你真的不应该在生产代码中这样做。 You violate all safety and can very easily crash the JVM if you are not very careful.如果您不非常小心,您就违反了所有安全性,并且很容易使 JVM 崩溃。

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.要读取/写入当前进程中的任意内存,您可以使用sun.misc.Unsafe类。 This is provided on all JVMs that I am aware of and have used.这在我知道并使用过的所有 JVM 上都提供。 An example on how to use the class can be found here .可以在此处找到有关如何使用该类的示例。

There are some IPC libraries which facilitate use of shared memory via memory-mapped files in Java.有一些 IPC 库可以通过 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. Chronicle Queue 类似于非阻塞 Java Queue ,除了您可以在一个 JVM 中提供消息并在另一个 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):在两个 JVM 中,您应该在同一个 FS 目录中创建一个ChronicleQueue实例(如果不需要消息持久性,请在内存挂载的 FS 中找到该目录):

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

Write a message in one JVM:在一个 JVM 中写一条消息:

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

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

Chronicle Map编年史地图

Chronicle Map allows IPC communication by some key. Chronicle Map 允许通过一些键进行 IPC 通信。 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, eg in /dev/shm/ ):在两个 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"));

Then in one JVM you could write:然后在一个 JVM 中你可以写:

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

On the reciever JVM:在接收者 JVM 上:

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

Distributed_cache is best solution to address your requirements. Distributed_cache是满足您需求的最佳解决方案。

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 allows threads in a cluster of JVMs to interact with each other across JVM boundaries using the same built-in JVM facilities extended to have a cluster-wide meaning Terracotta允许 JVM 集群中的线程使用相同的内置 JVM 设施跨 JVM 边界相互交互,这些设施扩展为具有集群范围的含义

Oracle_Coherence is a proprietary 1 Java-based in-memory data grid, designed to have better reliability, scalability and performance than traditional relational database management systems Oracle_Coherence是一个专有的1基于 Java 的内存数据网格,旨在比传统的关系数据库管理系统具有更好的可靠性、可扩展性和性能

Ehcache is a widely used open source Java distributed cache for general purpose caching, Java EE and light-weight containers. Ehcache是一种广泛使用的开源 Java 分布式缓存,用于通用缓存、Java EE 和轻量级容器。 It features memory and disk stores, replicate by copy and invalidate, listeners, cache loaders, cache extensions, cache exception handlers, a gzip caching servlet filter, RESTful and SOAP APIs它具有内存和磁盘存储、通过复制和失效复制、侦听器、缓存加载器、缓存扩展、缓存异常处理程序、gzip 缓存 servlet 过滤器、RESTful 和 SOAP API

Redis is a data structure server. Redis是一个数据结构服务器。 It is open-source, networked, in-memory, and stores keys with optional durability.它是开源的、联网的、内存中的,并存储具有可选耐久性的密钥。

Couchbase_Server is an open-source, distributed (shared-nothing architecture) multi-model NoSQL document-oriented database software package that is optimized for interactive applications. Couchbase_Server是一个开源、分布式(无共享架构)多模型 NoSQL 面向文档的数据库软件包,针对交互式应用程序进行了优化。 These applications may serve many concurrent users by creating, storing, retrieving, aggregating, manipulating and presenting data.这些应用程序可以通过创建、存储、检索、聚合、操作和呈现数据为许多并发用户提供服务。

Useful posts:有用的帖子:

What is Terracotta? 什么是兵马俑?

Is Terracotta a distributed cache? Terracotta 是分布式缓存吗?

infoq article资讯文章

Honestly, you don't want to share the same memory.老实说,您不想共享相同的内存。 You should send only the data that you need to the other JVM.您应该只将您需要的数据发送到另一个 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.发送数据两个 JVM 不共享相同的内存访问点,因此不可能将一个 JVM 的引用用于另一个 JVM。 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:但是,您可以将数据传送到另一个 JVM,然后以多种方式返回:

1) Using RMI , you can setup a remote server to parse data. 1) 使用RMI ,您可以设置远程服务器来解析数据。 I found it a bit of a hassle to set up because it requires security changes and that the data be Serializable .我发现设置有点麻烦,因为它需要更改安全性并且数据是Serializable You can find out more at the link.您可以在链接中了解更多信息。

2) Using a server is the age-old method of sending data to different places. 2) 使用服务器是将数据发送到不同地方的古老方法。 One way to implement this is using a ServerSocket and connecting with a Socket on localhost .实现此目的的一种方法是使用ServerSocket并与localhost上的Socket连接。 Objects still need to be Serializable if you want to use ObjectOutputStream .如果您想使用ObjectOutputStream对象仍然需要可Serializable


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 smUnsafe , using the correct memory addresses, you will be able to retrieve Objects stored by the backing C/C++ arrays in the OS.如果你想使用 Java 代码,你可以看看 using smUnsafe ,使用正确的内存地址,你将能够检索由操作系统中的支持 C/C++ 数组存储的对象。

Otherwise, you can use native methods to access the C/C++ arrays yourself, although I have no clue how this could be implemented.否则,您可以使用native方法自己访问 C/C++ 数组,尽管我不知道如何实现。

Jocket , an experimental project I made a few years ago does exactly this. Jocket ,我几年前做的一个实验项目就是这样做的。

It includes a drop-in replacement for java.net.Socket and java.net.ServerSocket if you want to use Input/OutputStream .如果您想使用Input/OutputStream它包括java.net.Socketjava.net.ServerSocket替代品。

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 .缓冲区是通过RandomAccessFile获得的。

It includes a small JNI layer (linux) to implement IPC synchronization (ie notify the other process of availability of data) but this is not mandatory if you want to poll for data.它包括一个小的 JNI 层 (linux) 来实现 IPC 同步(即通知其他进程数据的可用性)但是如果您想轮询数据,这不是强制性的。

Yes,是的,

with an intermediate program you can write to and read arbitrary memory locations.使用中间程序,您可以写入和读取任意内存位置。 You cannot do it purely in Java.你不能纯粹用 Java 来做。

For example you can write a piece of C++ code that can read an arbitrary memory location and call that via JNI.例如,您可以编写一段 C++ 代码,该代码可以读取任意内存位置并通过 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.exe(或 linux 等价物)为其生成标头:

javah MemTest

Now you write a .cpp file that includes that header and defines the methods.现在您编写一个 .cpp 文件,其中包含该标头并定义方法。 Compile to DLL.编译成DLL。 To load the .dll you either use the -Djava.library.path JVM parameter with appropriate value, or System.loadLibrary().要加载 .dll,您可以使用具有适当值的 -Djava.library.path JVM 参数或 System.loadLibrary()。

Note of caution: I do not recommend doing this.注意事项:我不建议这样做。 There are almost certainly better ways to do what you want to do.几乎肯定有更好的方法来做你想做的事。

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.使用 Unsafe 将 Object 字节复制到 off-head 区域怎么样,然后是一些如何将廉价指针和类名传递给第二个 JVM,后者将使用指针和类名来复制和转换堆外空间到 in-第二个 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:所以现在您将MyStructurep从 ((MyStructure)p.pointer).x 传递到第二个 JVM,您应该能够:

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.我可以想象一个用例:假设您有 2 个微服务,它们可能会或可能不会在同一台服务器上运行,还有一个客户端策略,可能在容器 AppServer 中实现,它知道服务部署在哪里,以防它检测到请求的服务在本地,它可能使用基于不安全的服务客户端透明地查询其他服务。 Nasty but interesting, I'd like to see performance implications of not using network, bypassing WebAPI (calling directly handling controller) and not serializing.讨厌但有趣,我想看看不使用网络、绕过 WebAPI(直接调用处理控制器)和不序列化对性能的影响。 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/https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/借来的代码片段

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM