两个JVM之间的共享内存

时间:2014-08-20 04:20:06

标签: java memory-management jvm

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

7 个答案:

答案 0 :(得分:28)

解决方案1:

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

服务器

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上提供的。可以找到有关如何使用该类的示例here

答案 1 :(得分:22)

有一些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

Chronicle Map

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
}

答案 2 :(得分:4)

老实说,你不想共享相同的内存。您应该只将您需要的数据发送到其他JVM。话虽如此,如果您需要共享内存,则存在其他解决方案。

发送数据 两个JVM不共享相同的内存访问点,因此无法使用一个JVM的引用在另一个JVM中使用。只会创建一个新的引用,因为它们彼此不了解。

但是,您可以将数据发送到其他JVM,并以各种方式返回:

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

2)使用服务器是一种将数据发送到不同地方的古老方法。实现此目的的一种方法是使用ServerSocket并与localhost上的Socket相关联。如果您想使用ObjectOutputStream,则对象仍需Serializable


分享数据 这是非常危险和不稳定的,低级别的,并且,不安全(字面意思)。

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

否则,您可以使用native方法自行访问C / C ++数组,但我不知道如何实现它。

答案 3 :(得分:4)

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

  

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

几个选项:

Terracotta允许JVM集群中的线程跨JVM边界相互交互,使用相同的内置JVM工具扩展为具有集群范围的含义

Oracle_Coherence是一个专有的1基于Java的内存数据网格,旨在提供比传统关系数据库管理系统更好的可靠性,可伸缩性和性能

Ehcache是一种广泛使用的开源Java分布式缓存,用于通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,通过复制和无效复制,监听器,缓存加载器,缓存扩展,缓存异常处理程序,gzip缓存servlet过滤器,RESTful和SOAP API

Redis是一个数据结构服务器。它是开源的,联网的,内存中的,并且存储具有可选耐久性的密钥。

Couchbase_Server是一个开源的,分布式(无共享架构)多模型NoSQL面向文档的数据库软件包,针对交互式应用程序进行了优化。这些应用程序可以通过创建,存储,检索,聚合,操作和呈现数据来为许多并发用户提供服务。

有用的帖子:

What is Terracotta?

Is Terracotta a distributed cache?

infoq文章

答案 4 :(得分:2)

是,

使用中间程序,您可以写入并读取任意内存位置。你不能完全用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()。

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

答案 5 :(得分:1)

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

如果您要使用java.net.Socket,它会包含java.net.ServerSocketInput/OutputStream的替代品。

每个定向信道使用一对循环缓冲区来发布和获取数据(一个用于&#34;数据包&#34;一个用于数据包的地址)。缓冲区通过RandomAccessFile获得。

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

答案 6 :(得分:0)

使用轮转堆外内存不安全

如何使用Unsafe将Object字节复制到off-head区域,然后一些如何将廉价指针和类名传递给将使用指针和类名复制并转换堆外空间的第二个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;
}

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

MyStructure locallyImported = (MyStructure)p.pointer;

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

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