java—两个JVM之间的共享内存

wdebmtf2  于 2021-06-29  发布在  Java
关注(0)|答案(7)|浏览(1412)

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

gj3fmq9x

gj3fmq9x1#

分布式缓存是满足您需求的最佳解决方案。
在计算中,分布式缓存是传统缓存概念在单一区域中的扩展。分布式缓存可以跨越多个服务器,这样它的大小和容量就可以增加。
选项很少:
terracotta允许jvm集群中的线程跨jvm边界进行交互,使用相同的内置jvm设施扩展到集群范围
oracle\u coherence是一个基于java的内存数据网格,它比传统的关系数据库管理系统具有更好的可靠性、可扩展性和性能
ehcache是一种广泛使用的开源java分布式缓存,用于通用缓存、javaee和轻量级容器。它具有内存和磁盘存储、按拷贝复制和失效、侦听器、缓存加载程序、缓存扩展、缓存异常处理程序、gzip缓存servlet过滤器、restful和soapis
redis是一个数据结构服务器。它是开源的、网络化的、内存中的,并且存储的密钥具有可选的耐久性。
couchbase\u服务器是一个开源的、分布式(无共享体系结构)的多模型nosql面向文档的数据库软件包,它针对交互式应用程序进行了优化。这些应用程序可以通过创建、存储、检索、聚合、操作和呈现数据来服务于许多并发用户。
有用的帖子:
什么是陶土?
terracotta是分布式缓存吗?
infoq文章

uxh89sit

uxh89sit2#

老实说,你不想分享同样的记忆。您应该只将需要的数据发送到另一个jvm。也就是说,如果您确实需要共享内存,那么就存在其他解决方案。
两个jvm不共享相同的内存访问点,因此不可能使用一个jvm的引用来在另一个jvm中使用。只需创建一个新的引用,因为他们彼此不了解。
但是,您可以将数据发送到其他jvm,然后以各种方式返回:
1) 使用rmi,您可以设置一个远程服务器来解析数据。我发现设置起来有点麻烦,因为它需要更改安全性,而且数据 Serializable . 你可以在链接上找到更多信息。
2) 使用服务器是将数据发送到不同地方的古老方法。实现这一点的一种方法是使用 ServerSocket 和一个 Socketlocalhost . 对象仍然需要 Serializable 如果你想用 ObjectOutputStream .
共享数据这是非常危险和不稳定的,低级别的,以及,嗯,不安全的(字面意思)。
如果您想使用java代码,可以看看 s.m.Unsafe ,使用正确的内存地址,您将能够检索操作系统中备份c/c数组存储的对象。
否则,您可以使用 native 方法来访问c/c
数组,尽管我不知道如何实现。

eeq64g8w

eeq64g8w3#

解决方案1:

在我看来,最好的解决方案是使用内存Map文件。这允许您在任意数量的进程(包括其他非java程序)之间共享一个内存区域。不能将java对象放入内存Map文件中,除非序列化它们。下面的示例显示您可以在两个不同的进程之间进行通信,但是您需要使其更加复杂,以便在进程之间进行更好的通信。我建议您看看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:

另一种解决方案是使用javasockets在进程之间来回通信。这样做还有一个额外的好处,就是可以很容易地通过网络进行通信。可以说,这比使用内存Map文件慢,但我没有任何基准来支持该语句。我不会发布代码来实现这个解决方案,因为实现一个可靠的网络协议可能会变得非常复杂,而且是相当特定于应用程序的。有许多好的网络站点可以通过快速搜索找到。
如果您想在两个不同的进程之间共享内存,那么上面的例子就是。如果您只想在当前进程中读/写任意内存,那么应该首先知道一些警告。这违背了jvm的整个原则,您真的不应该在生产代码中这样做。你违反了所有的安全性,如果你不小心的话很容易使jvm崩溃。
尽管如此,这是相当有趣的实验。要在当前进程中读取/写入任意内存,可以使用 sun.misc.Unsafe 班级。我知道并使用过的所有jvm都提供了这个功能。关于如何使用这个类的例子可以在这里找到。

ia2d9nvy

ia2d9nvy4#

有一些ipc库通过java中的内存Map文件来促进共享内存的使用。

历史记录队列

编年史队列类似于非阻塞java Queue ,但您可以在一个jvm中提供消息,并在另一个jvm中轮询它。
在这两个JVM中,您应该创建一个 ChronicleQueue 同一fs目录中的示例(如果不需要消息持久性,请在内存装载的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。

编年史Map

编年史Map允许ipc通过一些键进行通信。在这两个JVM中,您应该创建一个具有相同配置的Map,并将其持久化到同一个文件(如果您不需要实际的磁盘持久化,则该文件应该在内存挂载的fs中进行本地化,例如。g。在 /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
}
xdyibdwo

xdyibdwo5#

乔凯特,我几年前做的一个实验项目就是这样做的。
它包括替换 java.net.Socket 以及 java.net.ServerSocket 如果你想用 Input/OutputStream .
每个定向通道使用一对循环缓冲区来发布和获取数据(一个用于“数据包”,一个用于数据包地址)。缓冲器是通过 RandomAccessFile .
它包括一个小的jni层(linux)来实现ipc同步(即通知其他进程数据的可用性),但是如果您想轮询数据,这不是必需的。

li9yvcax

li9yvcax6#

pivot堆外内存不安全
如何使用不安全的方法将对象字节复制到非头部区域,然后将廉价的指针和类名传递给第二个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 从((mystructure)p.pointer).x到第二个jvm,您应该能够:

MyStructure locallyImported = (MyStructure)p.pointer;

我可以想象一个用例:假设你有两个微服务,它们可能在同一台服务器上运行,也可能不在同一台服务器上运行,还有一个客户端策略,可能在容器appserver中实现,它知道服务部署在何处,以防它检测到请求的服务在本地,它可能使用基于不安全的服务客户机透明地查询其他服务。讨厌但有趣的是,我想看看不使用网络、绕过webapi(直接调用处理控制器)和不序列化对性能的影响。在这种情况下,除了控制器参数外,还应提供控制器本身。根本没想过安全问题。
借用的代码段https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/

umuewwlo

umuewwlo7#

对,
使用中间程序,您可以写入和读取任意内存位置。你不能完全用java来做。
例如,您可以编写一段可以读取任意内存位置的c++代码,并通过jni调用它。同样的情况反过来也适用于写入内存地址。
首先为应处理此问题的类编写类定义,例如:

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

然后你编译它。然后使用javah.exe(或等效的linux)为其生成标头:

javah MemTest

现在编写一个.cpp文件,其中包含该头并定义方法。编译到dll。要加载.dll,可以使用-djava.library.path jvm参数和适当的值,也可以使用system.loadlibrary()。
注意:我不建议这样做。几乎可以肯定有更好的方法来做你想做的事。

相关问题