本日は、日本Javaユーザーグループ(JJUG)主催のナイトセミナー「JVM特集」に参加してきました。
今晩のセッションは次の2つでした。
- JNR: ネイティブコードをコールするのに、まだJNI使っているの?(by 櫻庭さん)
- HotSpot のロック: A Peek Under the Hood(by バックさん)
今回のナイトセミナーは、募集開始から1日足らずで定員を超過し、定員より多いキャンセル待ち人数となるものでした。このテーマでなぜ?という声もありましたが、会場で「JNI使ったことある人」に多数手が上がるなど、実はニーズが大きい分野だったのかもしれません。
JNR: ネイティブコードをコールするのに、まだJNI使っているの?
セッション資料: http://www.slideshare.net/skrb/jnr-java-native-runtime
日本で唯一のJava Championである櫻庭さんのセッションです。
Java標準のネイティブコードアクセスの仕組みであるJNI(Java Native Interface)と、JNIよりはるかに簡単にネイティブにアクセスできるJNA(Java Native Access)、そして、今回のセッションのテーマであるJNR(Java Native Runtime)について比較しながら紹介をされていました。
聞いた印象をまとめると次のイメージです。
仕組み | 簡単に使えるか | 性能は |
---|---|---|
JNI | とっても面倒 | 遅い |
JNA | 簡単 | とっても遅い |
JNR | 簡単 | 遅い(JNI+α) |
JNRの主開発者Charles Nutter氏は、JRuby開発者でもあり、OpenJDKのinvoke dynamicでも大きく貢献している人物とのことです。
JNRは、githubでソースが公開され、mavenリポジトリにバイナリが置かれています。現時点ではドキュメントが少なく、「ソース読め」の世界だそうです。
JNRは、Javaコードからネイティブコードの呼び出しの際に、ユーザーコード(Java)→JNR Stub→JNR Call→JNI Impl→libffi(jffi + libffi)→対象ライブラリ
という流れを経ます。
このffi(jffi?)の部分は、Java SE 10をターゲットにしているProject Panamaの一環としてJEP 191になっています。今のところJNRはJava SEへの取り込みはないようです。
JNRについては、汎用のネイティブコード(C言語インタフェース)呼び出しを記述するほか、主要なネイティブコードについては事前にJava側にラッパーが用意されています。例えば、POSIX API、UNIXドメインソケット、シグナルなどです。POSIX APIについては、POSIXをサポートしていないWindowsにおいても擬似的に実現しているそうです。
サンプルコードは、セッションではメモできなかったので後日資料へのリンクを追加する予定ですが、JNRのリポジトリにあるサンプル(セッションで紹介のあったgetpid)のURLを記載しておきます。
https://github.com/jnr/jnr-ffi-examples/blob/master/getpid/src/main/java/getpid/Getpid.java
HotSpot のロック: A Peek Under the Hood
セッション資料: https://blogs.oracle.com/buck/entry/japan_jug_presentation_on_hotspot
JRockit・HotSpotのサポートをしているDavid Buckさん、日本語ぺらぺらです。今年のJavaOneで話すセッションの内容を本日予行を兼ねてのセッションのようです。
まず、synchronizedブロックを使うとJavaバイトコードレベルではmonitorenter/monitorexitが生成されます。また、例外コードが付け加わります。
ここで使われるMonitorとは、排他制御(Mutal Execution)と条件変数(Condition Variable)を持つもので、排他制御はsynchronizedで使われ、条件変数はObject.wait/notify/notifyAllで使われます。
また、Monitorにはhappens-beforeが適用され、Monitorでロックをはずし、次に獲得したときはメモリ整合性が保障されます。
Monitorのデメリットは、テスト不可能、タイムアウトなし、キャンセル不可、再起できてしまう、リーダー/ライターに分けられない、セキュリティなどです。これらの特性が必要とされる場合、java.util.concurrentを使ってくださいとのことです。
ちなみにsynchronizedメソッドを使うと、Javaバイトコードレベルではmonitor命令ではなく、フラグ(ACC_SYNCHRONIZED)が加わりますが、振る舞いはmonitor命令と同じになるとのことです。
次にロックの種類です。次の3種類あります。上から下に向かって重い処理となります。
- Biased
- Thin
- Fat
FatはOSのスケジューリングに任せるロックなので、コンテクストスイッチが発生し重い処理となります。Thinは、スピンロック(スリープ)で実現され、コンテクストスイッチは入りません。Biasedはオブジェクト(マークワード)にロックを取得したスレッドIDを書き込み、スタックにロック情報を置くだけで実現する軽い処理です。一つのスレッドだけオブジェクトのロックを独占していることが前提となり、もし他のスレッドがロックを獲得しようとするとrevoke処理が発生します。revoke処理は全スレッドを停止させ、ロックの本当の状態を確認(スタックウォーキング)します。これはとても重い処理です。
どのロックを使うかは、HotSpotが判断します。
デフォルトでは、Biasedロックが使われ、実行時情報にもとづきアダプティブにThinロック、Fatロックが使われます。たとえば、競合が発生しやすい起動直後(起動後4秒)はBiased使用禁止、実行中にrevokeが走ったオブジェクトはBiased禁止にする(Thinを使う)などです。
ロックの状態は、オブジェクトのヘッダー(マークワード)とスタック上のロック情報などで管理しています。
なお、マークワードには、オブジェクトのidentityHashCodeを格納する領域があり、一度でもhashCodeが参照されるとそこが使われるため、Biasedロックがそのオブジェクトについては使えなくなるということがあります。
プロファイリングツールとしては、DTraceが使える環境ならば柔軟で強力(ただし導入は大変)、FlighRecorderとその可視化のMission Controlが開発・評価時は無料で(プロダクション環境でも評価は無料、ただし評価はどこまでかは不透明)使えるとのことです。
JavaVMオプションでの推奨は、java.util.concurrentのロックがスレッドダンプで表示されるPrintConcurrentLocksです。
競合の激しい環境でBiasedLockを無効にするUseBiasedLockingも知っておくと役に立つ
かもしれません。