2012年8月17日金曜日

JavaからJNI経由で共有メモリ for Win

javaからJNIで共有メモリを弄りたい。
そんな、ひと夏の衝動にかられて、JNIで共有メモリをアクセスしてみました。
https://github.com/tkymism/sharedmem

今回も流れるインタフェースを駆使して、こんな感じのインタフェースにしてみた訳です。



使い方
(1)VisualStudioでdll生成

  •   構成プロパティ➡C/C++-➡全般➡追加のインクルードディレクトリ
    • [jdk_dir]\include;
    • [jdk_dir]\include\win32;
    • [git_dir]\sharedmem\sharedmem-win\include
  •   構成プロパティ➡C/C++➡コード生成➡ランタイムライブラリ
    • マルチスレッド (/MT)
  •  構成プロパティ➡リンカー➡全般➡追加のライブラリディレクトリ
    • [jdk_dir]\lib;
  •  構成プロパティ➡リンカー➡入力➡追加の依存ファイル
    • jvm.lib;

(2)maven installでjar生成


解説
"hogehoge"という名前で共有メモリに書き込んでいます。0~99byteはヘッダーとして使用して、100byte目以降を実態のファイルとしてallocateする図です。
locateでは0~99byte目にアクセスしてByteBuffer(Direct)で共有メモリに直接attachします。 100byte目以降(locate)にもdataを突っ込んで、そしてreleaseする訳です。
やはり、共有メモリに対してアクセスするためには排他制御が必要です。今回は読み込みと書き込み両方でMutexを使って排他制御を行っています。
このプログラムではMutexの名前には"<共有メモリ名>+.lock"で生成します。
SharedMemoryRepository#readonly(String name)排他する
SharedMemoryRepository#writable(String name)排他する
SharedMemoryRepository#copy(String name)排他しない
WindowsAPIのOpenFileMappingとMapViewOfFileでは読み込みモードが一緒じゃないとアクセス権でエラーが出ました。なので、以下のようにopenした段階でモードを決定してmappingのモードと同じになるようにしています。 ByteBufferは非常に便利なんですが、共有メモリにjavaのObjectをserializedしたbyte配列を格納したいと思い立ったため、こんなインタフェースも追加してみました。

課題
NativeAPIを触るとやはりallocateの煩わしさにいらだちます。allocateする際にbyte列を指定するのですが、CreateFileMapping関数ではサイズを64bitで指定できるのですが、32bitづつ分けて設定してくれという大胆な関数でした。 そんな変態的な仕様に対して、javaからはlong(8byte)をint(4byte)に分割して状態でJNIに引き渡すようにしてみた訳ですが、本当に正しいかが疑問に残ります。 ほかにも色々課題はあるのですが、とりあえずこれでcommit.

共有メモリを使う動機
私の部署で開発しているパッケージは基本的にjava(Swing)で開発している訳ですが、既存製品から移行し続ける経緯もあり、未だに帳票処理やバッチ処理ではCOBOLで開発しています。旧モデルの保守性を考えると、ビジネスロジック(COBOL)の資産がそのまま残ってしまったということです。
さて、javaからCOBOLを呼び出す際にはJava->JNI(C)->COBOLという破廉恥な連携が行われている訳ですが、たまにヘタクソなコボラーさんが、添字エラーを起こしちゃうようなプログラムを作っちゃうと、javaごとプロセスが死んでしまうという大惨事が引き起こされます。
なので、COBOLの処理をなんとかして別プロセスで動作させたいという動機が生まれた訳です。
でも、javaで抱えるメモリは共有したい!ということで、Google App EngineなんかであるようなMemcacheを、Windowsの共有メモリを使って実現したいということになったわけです。

2012年8月13日月曜日

JNIでヒープ外を参照


JNIを使ってヒープ外のメモリにアクセスしていたわけで。そしたら、実装方法により性能が全然違ったのでメモ。



ByteBufferでアクセス

ByteBufferクラスを使ってアクセスするのが最高性能が出ました。以下に実装例を記載。
Java
C++(MSVC)

byte[]でアクセス

ByteBufferより倍の性能かかった実装例で、byte[]を引き渡す方式。C側でJavaの配列操作をするのに時間がかかるようです。
Java
C++(MSVC)



1 byteずつアクセス

最初はbyte数分、Java側でループを行って1byteずつ参照する方式。JNIのオーバヘッドが大きいようで、ByteBufferクラスに比べ100倍以上の時間がかかっていた。
Java
C++(MSVC)

2012年8月11日土曜日

Java外部起動

最近、Windowsの共有メモリ(BaseNamedObjects)をJavaでアクセスするプログラムを書いているのだけど、そうすると別プロセスの起動をJUnitで書きたくなる訳で。
Javaの別プロセスを起動する場合にこんな感じで書けたら良いなと思いながら書いたものをgithubに公開してみる、そんな夕下がり。
https://github.com/tkymism/java-launch

ここでのポイントは標準出力を親プロセスに引き渡すJavaMainクラス。(参考:ひしだま's 技術メモページ)
インタフェースは流してみる。
Linuxは現在、未対応でMacとWindowsで動作確認済。
標準出力で文字コードを正しく読み取るためには親プロセスと子プロセスで文字コードを合わせる必要があるようです。