FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

JavaOne 2015 サンフランシスコに参加しました!(2日目パート2)

Hello World! コンテンツ・メディア第1事業部の、jyukutyoこと阪田です。JavaOneは一番最初のセッションが始まるのは8:30、最後のセッションが終わるのは21:45です。お昼休みはありませんのでセッション間で昼食を済ませつつ、ずっとセッションを聴き続けるため、期間中はかなりハードです。観光などする時間はなく、お土産を買う時間を捻出するのもかなり大変でした。

Introduction to Modular Development

キーノートでもあった「Project Jigsaw」の紹介セッションです。構文はキーノートのレポートを参照してください。 今後java.lang.Objectなどはプラットフォームが提供するjava.baseモジュールにまとめられ、暗黙的に依存することとなります。モジュールのアクセシビリティは、モジュール内の公開されたパッケージのpublicなものだけです。公開されていないパッケージのpublicにはアクセスできないため、publicであるだけではアクセシビリティはありません。 モジュールはmodule-info.javaに記述し、コンパイルに含める必要があります。他のモジュールを使うときはjava -modulepath dir1:dir2のようにします。モジュールのmain()メソッドを実行するときはjava -m モジュール名/モジュールの完全修飾名で実行します。このあたりのことはProject Jigsawのクイックスタートページがわかりやすいです。-Xdiag:resolverオプションをつけると、依存関係の解決をログに出力することができるようです。

invokedynamic for Mere Mortals

JVM命令の1つであるinvokedynamic(indy)についてです。 JVMはもちろんJavaのために設計されたものですが、近年複数のプログラミング言語JVM上で動作するようになりました。いまやどんな種類のプログラミング言語にとってもすばらしいプラットフォームとなっています。 Java以外の言語にとっての要求は、例えばcontinuationsやdyanmic invocation、tail recursion、interface injectionなどです。dyanmic invocation(動的呼び出し)をするために動的型付けがあるわけですが、動的型付けでは、呼び出しを実行するその瞬間までその型がいったい何なのか知ることができません。動的型付けは型妨害ではないし、弱い型付けでもありません。動的型付け言語は完全な型情報がなく、より実行時のチェックが求められます。indyができるまではこういった言語をJVMで動作させるためにはリフレクションを使うしかありませんでした。ただし、リフレクションにすると動作が遅く、引数がすべてObjectであったりとさまざまな問題がありました。リフレクションではインライン化できず最適化できませんでした。


JSR-292でjava.lang.invokeAPIが追加されましたが、これはよりよいリフレクションと言えるかもしれません。そしてinvokedynamic命令が追加されました。これはリンケージのためにディスパッチするものです。indyJVM命令セットができてから初めて追加された命令で、そして初めてJava以外の言語をターゲットにした命令です。メソッドハンドルとコールサイト、そしてブートストラップメソッド(bsm)という概念があります。メソッドハンドルはメソッドを指し示すものです。関数ポインタ的な考えでよいと思います。メソッドハンドルのパフォーマンスですが、リフレクションよりはかなり早くなります。indyとは独立して使うことができます。コールサイトがメソッドハンドルを使ってメソッド呼び出しをします。動的という意味では、コールサイトは変わらずメソッドハンドルを変えることで呼び出すメソッドを換えれられます。つまり、コールサイトがメソッドハンドルを保持しています。


次にブートストラップのステップです。indyを使ったコードを実行するとき、ブートストラップメソッド(bsm)を呼び出します。bsmはコールサイトを返すメソッドであるため、コールサイトを通じてメソッドハンドルへ、メソッドハンドルからメソッドを呼び出すというステップです。2回目以降の実行は、bsmは呼び出さずにコールサイトを通じて呼び出すだけです。リンケージは呼び出しではなく、リンケージは一度だけ実行する必要があるもので、またコストの高い処理でもあります。一方呼び出しは何度も実行するもので、jmp/callを必要とします。indyによってリンケージが変えました。java.lang.invokeAPIindyなしでもよく使われます。JVMはどの言語にとってもすばらしいプラットフォームと言えるでしょう。

Compact Strings: A Memory-Efficient Internal Representation for Strings

JEP(JDK Enhancement Proposals) 254: Compact Stringsについてです。Stringの内部表現でもっと効果的にリソースを使うようにしようというものです。個人的にはこれが2日目で一番興味深かったセッションですね。JDK9でのリリースが予定されています。 JavaUTF-16をサポートしており、2バイト文字を使います。より効果的にリソースを使うようにしつつも、完全な後方互換性を保つようにします。どんな使用状況でも以前のスループットパフォーマンスは維持します。JDK6で失敗したCompressed Stringsの置き換えです。さまざまなアプリケーションから統計をとったところ、Stringの大部分は小さいもので、75%以上は35文字未満でした。また、x64でcompressd referenceを使わないと2倍メモリを使うことになります。


新しいStringクラスのデザインは次のようになります。後方互換性を維持しつつも、内部表現は変更します。UTF-16だけで表現するのではなく、 UTF-16またはISO-8859-1/Latin-1を使うようにします。1バイト文字はISO-8859-1/Latin-1で、2バイト文字はUTF-16とし、char配列ではなくbyte配列で表現するようにします。さらにencodingというbyteのフィールドを追加し、何のエンコーディングか示唆するようにします。なぜUTF-8ではないのかというと、UTF-8は文字幅が可変なためです。StringのAPIは文字シーケンスにランダムアクセスするものがたくさんあります。Stringクラスはこう変わるイメージです。JDK8ではStringクラスはこうでした。

{
 private final char value[];
 private int hash;
 ...
}

JEP 254ではこうなります。

{
 private final byte[] value;
 private final byte coder;
 private int hash;
 ...
}

さきほどJDK6での失敗と言いましたが、具体的に見てみます。JDK6のCompressed Stringsがないときはこうでした。

{
 private final char value[];
 private final int offset;
 private final int count;
 private int hash;
 ...
}

Compressed Stringsでこうなりました。

{
 private final object value;
 private final int offset;
 private final int count;
 private int hash;
 ...
}

valueがObjectであり、byteかcharかを判断する必要がありました。そのためCompressed StringsではStringクラスの2つの実装があることとなります。メンテナンスのコストもかかります。JREでのサポートに限界もありました。JEP 254ではbyte[]のみとなります。encodingフィールドも追加します。JREでcompressed stringsへのサポートも拡張できるでしょう。


JMH(Javaのマイクロベンチマークツール)でのベンチマークでは、すべてのケースで処理が早くなっています。メモリ使用量は21%削減できました。スループットも5%増加してよくなっています。

jyukutyoコメント

2日目に出たセッションからいくつかをピックアップしてレポートしました。これでまだ2日目!あと3日分あります。長いレポートになりますが、ぜひ最後までお付き合いください。