JJUG Night Seminarへ行ってきました

先週の水曜日、「JJUG Night Seminar -Java VM & LT & 納涼会-」へ行ってきました。

当日は、ほろ酔いで帰ってきて、スタックマシンを書いて遊んだら満足して寝てしまったので、改めてセッションのメモと感想を書いておきます。

# パソコンを忘れて行ったこともあり、メモをあまり取っておらず、記憶に頼って書きました。間違いがありましたら、ぜひ教えてください。

さくらばさんのJavaのStack Machineのお話

スタックマシンとしてのJVMバイトコードをどうやって実行するか、というテーマでした。

いつも通り、音楽と写真が素敵なプレゼンテーションでした。

余談ですが、当日皆に忘れ去られていたJava 1.4のコードネームは、Merlinでしたね。

いろいろなデータ構造と、それに対応するJavaの実装クラス

データ構造 実装クラス
キュー ArrayBlockingQueue
双方向キュー/デック LinkedBlockingDeque
スタック ArrayDeque
リングバッファ なし
連想配列 HashMap
連結リスト LinkedList

口頭でいくつか補足されたことがありました。

1つ目は、LinkedListでキューを実装したときは、キューらしくない振る舞いを避けよう、という話です。キュー、双方向キュー、スタックなどは、表に挙げたクラスのほかに、LinkedListでも実装できます。ただし、LinkedListを使うと、リストの途中の値を出し入れできてしまいます。そういったことはやめておいた方がよいでしょうとのことでした。

2つ目は、スタックを実装するときにjava.util.Stackを使わないでね、という話です。常識だからか、理由の説明は省略されましたが、調べた上で、身内のJavaに詳しい人に教えてもらいました。

Vector、Stack、HashTableを現在使わない理由は、主に2つだそうです。

  • Collection Frameworkが定義するインタフェースに対してコーディングするのが良い作法だから、使わない方がよい(Vector、Stack、HashTableは、JavaにCollection Frameworkが導入される前から存在する)
  • Vector、Stack、HashTableは、メソッド単位での同期を行うが、同期が必要ならば、インスタンスを同期するなりAtmicReferenceを使うなりするのが現実的なので、使う意味がない

使ってはいけないというよりも、使う理由がないって感じなんですね。(合っているでしょうか?)

ちなみに、StackOverFlowでは、次の解答が支持を集めていました。

(2012/09/07 追記) さくらばさんからコメントで補足を頂きました。

  • Vectorはすべてのメソッドが同期化しているため同期化が必要ない場合はパフォーマンスが悪い。この場合はArrayListなどを使うべき
  • 同期化も単にメソッドをsynchornizedしているだけなので、add if absentのようなトランザクションに対しては意味をなさない。
  • 全部synchronizedしてしまっているので、複数スレッドが同時にアクセスするような場合はボトルネックになってしまう。通常はreadは複数スレッドでもOK、writeは1つのスレッドというようにしないとパフォーマンスが伸びません。これに対して、同期が必要な場合はjava.util.concurrentパッケージで定義されているコレクションクラスを使います。ListであればCopyOnWriteArrayListなど、MapはConcurrentHashMapなどを使用します。
http://d.hatena.ne.jp/torazuka/20120903/jjug#c1346765294
スタックマシンの動作

四則演算を例に、スタックマシンの動作が解説されました。

スタックマシンのハードウェアは、今ではもうほとんどないそうです。しかし、仮想マシンの実装にはまだまだあり、そのひとつがJVMです。

JVMには、2種類のスタックがあります。

  • Java Stack (for Collection)
  • Operand Stack

JVMで使われるスタックの動きは、こうなります。

  1. スレッドの中には、プログラムカウンタとJavaスタックがあり、メソッド呼出しごとにJavaスタックにFrameが入っていきます。
  2. Frameの中には、オペランドが格納されるOperand Stackと、ローカル変数が格納されるスタック配列があります。
  3. メソッドの戻り値がスタックに積まれます。

……と、このあたりは、やっぱり是非さくらばさんの発表資料でご覧ください。写真のおかげで、とても分かりやすかったです。

また、javapのお話が少しあって、これも勉強になりました。

  • 行番号みたいな「0: ... 2: ...」という数字
    • ワードの数の分だけ、数字が飛んでいる
  • Javaコードのbyte、short、booleanは、JVMの中ではintに格上げされる
    • booleanは必ず格上げされますが、byte、short、charはされないこともあるそうです(コメント欄参照)
  • 「bipush 10」?
    • 小さな値の代入は、constant poolを参照せずに直接行われる(# 即値ってことでしょうか?)


JVMが行う3種類の最適化について(inline、Escape Analysis、Tail Call)
インライン化
繰り返し実行されるメソッド呼出しがあり、呼び出されるメソッドの処理内容が簡単な場合、実行時に呼出し先のコードを呼び出し元に展開します。

インライン化が発生する条件は、おそらく、バイトコードに変換したときに、よりサイズが小さくなると計算された場合、だそうです。

Escape Analysis(Java 7〜)
生成したオブジェクトがFrameから逃げださないのであれば、OperandStackに置きます。

「Frameから逃げ出さない」という言葉の意味がピンとこなかったのですが、「そのオブジェクトが別のFrameから参照されることがない」という意味でしょうか。

Tail Call(Java 8〜?)
末尾再帰のコードをループとして実行します。末尾再帰によるメソッド呼出しのたびに新しいFrameが作られて、ついにはStackOverFlowが発生するような事態が、これで回避される可能性があります。

このくだりで、うまく言葉にできなくて、質問できなかった疑問があります。

さくらばさんは、次のようにおっしゃったと思います。

末尾再帰の最適化をJVMが実装することで、これまで末尾再帰を独自に最適化していた(Java以外の)言語も、インライン化などのJVMの最適化の恩恵を受けられるようになります。

(ちょっと違うかもしれませんが…)

これは、JVM上で動く言語が、自前で末尾再帰を最適化していた場合は、JVM上での最適化の恩恵を受けられない、というように聞こえました。groovycもscalacも、JVMで実行可能なコードを出力するのに、何か手を加えたが最後、JVMでの最適化はされなくなってしまうのでしょうか? もしそうなら、どうしてそうなるのでしょう?

末尾再帰の最適化が行う処理は、再帰をループに変更して、メソッド呼び出しのFrameの積み上げを減らす作業です。これは、メソッドのインライン化がもたらす結果と似ています。つまり、コンパイラが末尾再帰を最適化すると「JVMのインライン化が効かない」のではなく、「JVMがインライン化を行う余地が減る」という感じでしょうか。……身内からはそういったコメントをもらいましたが、サテハテ。ここは、よく分かりませんでした。

(2012/09/07 追記) さくらばさんからコメントで補足を頂きました。

このように実行時にJavaであれば必要がない処理を必要とする場合あるため、Javaと同じような最適化ができない場合があるということです。

http://d.hatena.ne.jp/torazuka/20120903/jjug#c1346765294

(このように、の詳細は、コメント欄を参照ください)…動的な動作をさせるための実行時の処理の結果、最適化しにくいコードが生成されることもある、ということなのですね。invokedynamicについては、発表でも触れられていましたが、今回のまとめでは(自分の理解が浅いので!)省いてしまいました。スミマセン。勉強します。

ゆろよろさんのJava 7脆弱性のLT

最近話題になったJava 7の脆弱性http://jvn.jp/cert/JVNTA12-240A/)と、その実証コードの解説でした。

あるURIにアクセスしたら、Mac OSの電卓アプリが起動するというデモを見せていただきました。ある意味、いちばん納涼な感じのお話でしたね。

SecurityManagerのsetPermissionをし忘れている状態で、AllPermissionを与えられてしまうと危ない、というお話だったと思いますが、自分にはうまく説明できません。Javaのセキュリティ機構をもうちょっと勉強しないと、と思いました。

nekopさんのモジュールシステムのLT

クラスパスは死んだ、これからはモジュールシステムだよってお話でした。

モジュールシステムの例として、OSGiMaven、Jigsaw、JBossS Modulesが挙げられていました。

説明の内容はほとんどわかりませんでした。残念。誠に遺憾。

閑話休題。。。

nekopさんのLTが難しかった><という泣き言から始まって、モジュールシステムがなぜうれしいのかという話を身内としていたところ、.NETがその点では進んでいる(ただし別の地獄が待っている)という話を聴けて、勉強になりました。

アセンブリ構成ファイルについては、.NETに触れる前に必ず思い出すことにします。

cero-tさんのlong型への値代入のLT

32bitのOS上では、long型への値への代入が上位32bitと下位32bitに分けて行われるため、想定しない値になることがあります。なので、volatileを使いましょう、というお話でした。ナナンダッテー??

後から解説エントリを上げてくださっていました。

longへの値代入をアトミックに行いたいときは、AtomicLong使おうって話ですね。

別の機会に、ぜひBTraceの発表も聴きたいです。

高橋さんのネイティブコードからのJVM起動のLT

Javaからネイティブコードを動かすケースはよくありますが、逆のケースもあるんですね。

  1. jni.hをインクルードする
  2. JVMに渡すオプションを設定
  3. JVMを起動するAPIに渡すオプションを設定
  4. JVMにクラスをロードする
  5. メソッドIDを取得する
  6. メソッドを実行する
  7. JVMを破棄する

といった手順を説明してくださいました。

感想まとめ

今回のセミナーは、2年前にゆろよろさんが主催されたJVM勉強会によく似た雰囲気でした。

テーマも会場も同じだし、何より、発表してくださった方が3名も同じ方だったからですね。

上の日記を見返すと、当時は意味不明だった話が部分的に分かるようになっていて、一瞬喜んだのですが、nekopさんのLTの感想は前回と差分ゼロでした。

ほとんどわかりませんでした。吊ってくる。

まるで成長しとらん。

冗談はさておき、JVMを理解するには、さまざまな知識が必要です。スタックマシン、最適化、GC、スレッド、クラスロード、バイナリ等々。今回語られた要素もあれば、語られなかった要素もあります。

これらの複合的な知識をパッチワークのように拾い集めることで、JVMの動きをイメージできるようになるのでしょう。一度ですべては理解できないので、何周もして少しずつ埋めていく感じだと思います。自分の場合、先日バイナリ勉強会に参加したおかげで、さくらばさんの説明のjavapのくだりが、すんなり理解できました。

というわけで、次のJVM勉強会(?)までには、クラスロードやセキュリティ機構をもっと理解しておきたいです。さくらばさんのFrameの解説のおかげで、JDIのAPIリファレンスの意味がかなり分かるようになったし、きっといけるんじゃないでしょうか :-P

最後になりましたが、発表してくださった皆さん、オラクルさん、ありがとうございました。JJUGさん、ビールとお菓子ごちそうさまでした。