Javaで、2つのJVM(同じ物理マシン上で実行)が同じメモリアドレススペースを使用/共有する方法はありますか? JVM1のプロデューサーが特定の事前定義されたメモリ位置にメッセージを置くと仮定します。JVM2のコンシューマは、どのメモリ位置を調べるべきかを知っている場合、メッセージを取得できますか?
私の意見では、メモリマップファイルを使用するのが最善の解決策です。これにより、他の非Javaプログラムを含む、任意の数のプロセス間でメモリ領域を共有できます。マップされたメモリにJavaオブジェクトを配置することはできません次の例は、2つの異なるプロセス間で通信できることを示していますが、プロセス間の通信を改善するには、さらに洗練させる必要があります。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 を使用してプロセス間でやり取りすることです。これには、ネットワークを介した通信が非常に簡単にできるという利点があります。これは、メモリマップファイルを使用するよりも遅くなりますが、そのステートメントをバックアップするベンチマークがありません。信頼できるネットワークプロトコルを実装するのは非常に複雑になる可能性があり、アプリケーション固有のコードなので、クイック検索で見つけることができる多くの優れたネットワークサイトがあります。
2つの異なるプロセス間でメモリを共有する場合の上記の例です。現在のプロセスで任意のメモリを読み書きしたいだけの場合、最初に知っておくべき警告がいくつかあります。これは、JVMの原則全体に反するため、実際に運用コードでこれを行うべきではありません。すべての安全性に違反しており、注意を怠るとJVMを簡単にクラッシュさせる可能性があります。
とはいえ、試してみるのはとても楽しいです。現在のプロセスの任意のメモリを読み書きするには、Sun.misc.Unsafe
クラス。これは、私が認識し使用しているすべてのJVMで提供されます。クラスの使用方法の例は here にあります。
Javaのメモリマップファイルを介した共有メモリの使用を容易にするいくつかのIPCライブラリがあります。
Chronicle Queueは非ブロッキングJava Queue
と似ていますが、1つのJVMでメッセージを提供し、別のJVMでポーリングできることを除きます。
両方のJVMで、同じFS=ディレクトリにChronicleQueue
インスタンスを作成する必要があります(このディレクトリは、メモリにマウントされたFS 「メッセージの永続化は必要ありません」:
ChronicleQueue ipc = ChronicleQueueBuilder.single("/dev/shm/queue-ipc").build();
1つの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機能も提供します。ChronicleQueueに似ています。重要な違いの1つは、メッセージのマーシャリング/デマーシャリングに [〜#〜] 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"));
次に、1つの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キャッシングサーブレットフィルター、RESTfulおよびSOAP API
Redis はデータ構造サーバーです。オープンソースで、ネットワーク化され、メモリ内にあり、オプションで耐久性のあるキーを保存します。
Couchbase_Server は、対話型アプリケーション向けに最適化された、オープンソースの分散(シェアードナッシングアーキテクチャ)マルチモデルNoSQLドキュメント指向データベースソフトウェアパッケージです。これらのアプリケーションは、データを作成、保存、取得、集約、操作、および提示することにより、多くの同時ユーザーにサービスを提供できます。
便利な投稿:
infoq 記事
正直なところ、同じメモリを共有したくありません。必要なデータのみを他のJVMに送信する必要があります。そうは言っても、共有メモリが必要な場合doには、他のソリューションが存在します。
データの送信2つのJVMは同じメモリアクセスポイントを共有しないため、あるJVMからの参照を別のJVMで使用することはできません。彼らはお互いを知らないので、新しい参照は単に作成されます。
ただし、他のJVMにデータを出荷し、さまざまな方法で戻すことができます。
1) [〜#〜] rmi [〜#〜] を使用して、データを解析するリモートサーバーをセットアップできます。セキュリティの変更が必要であり、データがSerializable
であるため、設定するのは少し面倒です。リンクで詳細を確認できます。
2)サーバーを使用することは、さまざまな場所にデータを送信する昔からの方法です。これを実装する1つの方法は、 ServerSocket
を使用し、Socket
で localhost
を使用して接続することです。 Serializable
を使用する場合、オブジェクトはObjectOutputStream
である必要があります。
データの共有これは非常に危険で揮発性があり、低レベルであり、安全ではありません(文字通り)。
Javaコードを使用する場合は、 s.m.Unsafe
、正しいメモリアドレスを使用すると、OSのバッキングC/C++配列によって保存されたオブジェクトを取得できます。
それ以外の場合は、native
メソッドを使用してC/C++配列に自分でアクセスできますが、これを実装する方法はわかりません。
はい、
中間プログラムを使用すると、任意のメモリ位置への書き込みと読み取りができます。 Javaだけではできません。
たとえば、任意のメモリ位置を読み取り、JNI経由で呼び出すことができるC++コードを作成できます。メモリアドレスに書き込む場合も同様です。
これを処理するクラスのクラス定義を最初に記述します。次に例を示します。
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()を使用します。
注意事項:これを行うことはお勧めしません。あなたがやりたいことをするより良い方法はほぼ確実にあります。
Jocket 、私が数年前に作った実験プロジェクトはまさにこれを行います。
Java.net.Socket
を使用する場合は、Java.net.ServerSocket
およびInput/OutputStream
のドロップイン置換が含まれます。
各方向チャネルは、循環バッファのペアを使用して、データをポストおよび取得します(1つは「パケット」用、もう1つはパケットのアドレス用)。バッファーはRandomAccessFile
を介して取得されます。
IPC同期(つまり、データの可用性を他のプロセスに通知する)を実装する小さなJNIレイヤー(Linux)が含まれていますが、データをポーリングする場合は必須ではありません。
ピボットオフヒープメモリでは安全ではありません
Unsafeを使用してオブジェクトバイトをオフヘッドゾーンにコピーしてから、ポインターとクラス名を使用してオフヒープスペースをコピーし、インヒープにキャストする2番目のJVMに安価なポインターとクラス名を渡す方法2番目の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).xからMyStructure
とp
を2番目のJVMに渡すと、次のことができるはずです。
MyStructure locallyImported = (MyStructure)p.pointer;
ユースケースを想像できます:同じサーバーで実行されている場合とされていない場合がある2つのマイクロサービスと、要求されたサービスを検出した場合にサービスがデプロイされる場所を知っているコンテナAppServerに実装されたクライアント戦略があるとしますローカルにある場合、Unsafeベースのサービスクライアントを使用して、他のサービスを透過的に照会できます。厄介ですが興味深いのは、ネットワークを使用しないこと、WebAPIをバイパスすること(コントローラーを直接処理することを呼び出すこと)、およびシリアル化しないことのパフォーマンスへの影響を確認することです。この場合のコントローラーパラメーターとは別に、コントローラー自体を提供する必要があります。セキュリティについても考えませんでした。
https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-Java/ から借用したコードスニペット