[英]Shared Memory between two JVMs
JAVA 中有沒有辦法讓兩個 JVM(在同一台物理機器上運行)使用/共享相同的內存地址空間? 假設 JVM1 中的生產者將消息放在特定的預定義內存位置,如果 JVM2 上的消費者知道要查看哪個內存位置,是否可以檢索消息?
我認為最好的解決方案是使用內存映射文件。 這允許您在任意數量的進程之間共享內存區域,包括其他非 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' );
}
}
另一種解決方案是使用 Java Sockets在進程之間來回通信。 這有一個額外的好處,即允許非常輕松地通過網絡進行通信。 可以說這比使用內存映射文件慢,但我沒有任何基准來支持該聲明。 我不會發布實現這個解決方案的代碼,因為實現一個可靠的網絡協議會變得非常復雜並且是特定於應用程序的。 可以通過快速搜索找到許多不錯的網絡站點。
現在上面的例子是如果你想在兩個不同的進程之間共享內存。 如果您只想讀取/寫入當前進程中的任意內存,您應該首先了解一些警告。 這違背了 JVM 的整個原則,你真的不應該在生產代碼中這樣做。 如果您不非常小心,您就違反了所有安全性,並且很容易使 JVM 崩潰。
話雖如此,實驗還是很有趣的。 要讀取/寫入當前進程中的任意內存,您可以使用sun.misc.Unsafe
類。 這在我知道並使用過的所有 JVM 上都提供。 可以在此處找到有關如何使用該類的示例。
有一些 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
}
Distributed_cache是滿足您需求的最佳解決方案。
在計算中,分布式緩存是在單一語言環境中使用的傳統緩存概念的擴展。 分布式緩存可以跨越多個服務器,因此它可以在規模和跨國容量方面增長。
幾個選項:
Terracotta允許 JVM 集群中的線程使用相同的內置 JVM 設施跨 JVM 邊界相互交互,這些設施擴展為具有集群范圍的含義
Oracle_Coherence是一個專有的1基於 Java 的內存數據網格,旨在比傳統的關系數據庫管理系統具有更好的可靠性、可擴展性和性能
Ehcache是一種廣泛使用的開源 Java 分布式緩存,用於通用緩存、Java EE 和輕量級容器。 它具有內存和磁盤存儲、通過復制和失效復制、偵聽器、緩存加載器、緩存擴展、緩存異常處理程序、gzip 緩存 servlet 過濾器、RESTful 和 SOAP API
Redis是一個數據結構服務器。 它是開源的、聯網的、內存中的,並存儲具有可選耐久性的密鑰。
Couchbase_Server是一個開源、分布式(無共享架構)多模型 NoSQL 面向文檔的數據庫軟件包,針對交互式應用程序進行了優化。 這些應用程序可以通過創建、存儲、檢索、聚合、操作和呈現數據為許多並發用戶提供服務。
有用的帖子:
資訊文章
老實說,您不想共享相同的內存。 您應該只將您需要的數據發送到另一個 JVM。 話雖如此,如果您確實需要共享內存,則存在其他解決方案。
發送數據兩個 JVM 不共享相同的內存訪問點,因此不可能將一個 JVM 的引用用於另一個 JVM。 將簡單地創建一個新的引用,因為它們彼此不了解。
但是,您可以將數據傳送到另一個 JVM,然后以多種方式返回:
1) 使用RMI ,您可以設置遠程服務器來解析數據。 我發現設置有點麻煩,因為它需要更改安全性並且數據是Serializable
。 您可以在鏈接中了解更多信息。
2) 使用服務器是將數據發送到不同地方的古老方法。 實現此目的的一種方法是使用ServerSocket
並與localhost
上的Socket
連接。 如果您想使用ObjectOutputStream
對象仍然需要可Serializable
。
共享數據這是非常危險和不穩定的,低級別的,而且,不安全(字面意思)。
如果你想使用 Java 代碼,你可以看看 using smUnsafe
,使用正確的內存地址,你將能夠檢索由操作系統中的支持 C/C++ 數組存儲的對象。
否則,您可以使用native
方法自己訪問 C/C++ 數組,盡管我不知道如何實現。
Jocket ,我幾年前做的一個實驗項目就是這樣做的。
如果您想使用Input/OutputStream
它包括java.net.Socket
和java.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);
}
然后你編譯它。 然后使用 javah.exe(或 linux 等價物)為其生成標頭:
javah MemTest
現在您編寫一個 .cpp 文件,其中包含該標頭並定義方法。 編譯成DLL。 要加載 .dll,您可以使用具有適當值的 -Djava.library.path JVM 參數或 System.loadLibrary()。
注意事項:我不建議這樣做。 幾乎肯定有更好的方法來做你想做的事。
不安全的樞軸堆外內存
使用 Unsafe 將 Object 字節復制到 off-head 區域怎么樣,然后是一些如何將廉價指針和類名傳遞給第二個 JVM,后者將使用指針和類名來復制和轉換堆外空間到 in-第二個 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;
我可以想象一個用例:假設您有 2 個微服務,它們可能會或可能不會在同一台服務器上運行,還有一個客戶端策略,可能在容器 AppServer 中實現,它知道服務部署在哪里,以防它檢測到請求的服務在本地,它可能使用基於不安全的服務客戶端透明地查詢其他服務。 討厭但有趣,我想看看不使用網絡、繞過 WebAPI(直接調用處理控制器)和不序列化對性能的影響。 在這種情況下,除了控制器參數外,還應提供控制器本身。 甚至沒有考慮安全。
從https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/借來的代碼片段
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.