More Related Content
Similar to ラムダと invokedynamic の蜜月
Similar to ラムダと invokedynamic の蜜月(20)
More from Taku Miyakawa(17)
ラムダと invokedynamic の蜜月
- 2. 自己紹介
• 宮川 拓 (@miyakawa_taku) と申します
• SI 屋です
• JJUG 幹事です
• Kink という JVM 言語を開発しています
– https://bitbucket.org/kink/kink
1
- 3. 要旨
• ラムダ式の実行は invokedynamic で実現さ
れます。その理由と、実行の流れを見ます
– 論点整理
– ラムダ式の実行 (1)
– invokedynamic の復習
– ラムダ式の実行 (2)
– なぜ invokedynamic?
– ラムダの直列化
2
※注記
この資料は、 JDK, JRE の「仕様」と「実装」を厳密に区別していません。
- 6. 実行の流れ
5
Comparable<String> c = (x, y) -> x.length() - y.length();
Collections.sort(strings, c);
main Collections
ラムダ
sort compare / 処理の中身の実行
new / ラムダ式の実行
今回の主な論点
- 9. 匿名クラスをコンパイル
• 匿名クラスは、「外側のクラス名$連番」という
名前で、コンパイラによって生成されます
8
$ cat >AnonClass.java
import java.util.*;
class AnonClass {
Comparator<String> comparator() {
return new Comparator<String> {
@Override public int compare(String x, String y) {
return x.length() – y.length();
}
};
}
}
$ javac AnonClass.java && ls *.class
AnonClass$1.class
AnonClass.class
- 12. ラムダのクラスの名前
• まずはラムダのクラスの名前を確認します
11
$ cat >Lambda.java
import java.util.*;
public class Lambda {
public static void main(String[] args) {
Comparator<String> c = (x, y) -> x.length() - y.length();
System.out.println(c.getClass());
}
}
$ javac Lambda.java && java Lambda
class Lambda$$Lambda$1
- 16. javap による逆アセンブルの結果
15
class Lambda {
...
java.util.function.IntUnaryOperator adder(int);
Code:
0: iload_1
1: invokedynamic #2, 0
6: areturn
private static int lambda$0(int, int);
Code:
0: iload_1
1: iload_0
2: iadd
3: ireturn
}
- 17. 再度 Java プログラム風に解釈
• ラムダの処理の中身は lambda$0 というメ
ソッドに記述されます
• ラムダ式の実行は invokedynamic 命令の呼
び出しになっています
16
class Lambda {
IntUnaryOperator adder(int delta) {
return <invokedynamic>(delta);
}
private static int lambda$0(int delta, int n) {
return n + delta;
}
}
- 21. invokedynamic とは
• 本来は、 JRuby など、 Java 以外の言語処理
系のために、 Java SE 7 で追加されたメソッド
呼び出し命令です
• invokevirtual, invokeinterface など、 Java SE
6 までのメソッド呼び出し命令と異なり、呼び
出す処理が実行時に選択できます
20
- 23. Java SE 6 までの呼び出し命令
• Java SE 6 までの呼び出し命令は、いずれも
Java 言語と密に結びついてます
– メソッドは再定義されない
– 名前、引数の型、レシーバのクラスが決まれば、呼
び出すべき処理が定まる
22
invokestatic static メソッドを呼び出す
invokespecial コンストラクタ、 private メソッド等を呼び出す
invokevirtual クラスに属するメソッドを呼び出す
invokeinterface インタフェースに属するメソッドを呼び出す
- 24. Java 以外の言語処理系の実装
―Java SE 6 以前
• Java 言語にない機構(メソッド再定義など)を
実現するため、処理系が呼び出しに介入
→JVM による実行時最適化が効きづらい
23
array.join
invoke
virtual
処理系
size Func@42
join Func@123
検索
def join
...
invoke
virtual
関数テーブル
- 25. Java 以外の言語処理系の実装
―Java SE 7 以降
• invokedynamic を使って、処理系を介さずに、
直接メソッドが呼び出せるようになりました
→JVM による実行時最適化が効きやすい!
24
array.join
invokedynamic def join
...
- 26. invokedynamic の道具立て
• 呼び出し元 (CallSite) ごとに、ブートストラップ
メソッドで、呼び出し先の関数ポインタ
(MethodHandle) を登録
25
Method
Handle
オブジェクト
CallSite
オブジェクト
ブートストラップ
メソッド <<create>>
初回呼び出しの前に
実行
対象の
処理
呼び出し元
<<create>>
呼び出し
- 27. ブートストラップメソッド
• static である必要がある
• ブートストラップメソッドの引数
– Lookup: MethodHandle のファクトリ
– String: 「メソッド名」だが、使わなくても可
– MethodType: invokedynamic の引数型と戻り値型
– 任意個数の定数
• ブートストラップメソッドの戻り値
– MethodHandle の初期値が紐付けられた CallSite
26
- 28. ブートストラップメソッドの例
• メソッドを呼び出した後、強制的に戻り値を
42 にする MethodHandle を生成
27
static CallSite bsm(Lookup lu, String name, MethodType mt)
throws Exception {
MethodHandle vmh = lu.findVirtual(mt.parameterType(0), name, vmt);
return new ConstantCallSite(filterReturnValue(vmh,
dropArguments(constant(int.class, 42), 0, int.class)));
}
• 以上のように、個々の invokedynamic の動作は、
ブートストラップメソッドを見れば見当が付きます
- 31. ラムダ式の実行の流れ
• ラムダ式の実行の invokedynamic には、
java.lang.invoke.LambdaMetaFactory の
metaFactory メソッドがブートストラップメソッ
ドとして紐付いています
30
invoke
dynamic
<<ブートストラップ>>
LambdaMetaFactory
#metaFactory
ラムダの
インスタンス化
2 回目以降の
実行
1. ラムダのクラスを生成
2. ラムダをインスタンス化する
MethodHandle を生成
- 32. LambdaMetaFactory
#metaFactory
31
CallSite metafactory(Lookup lookup, // MethodHandle のファクトリ
String name, // 関数型インタフェースの唯一の抽象メソッド (SAM) の名前 (例: compare)
MethodType invokedType, // 命令の引数・戻り値型
MethodType samMethod, // SAM の引数・戻り値型
MethodHandle implMethod, // 処理本体のメソッド (例: lambda$0)
MethodType instantiatedSamType) // 型パラメータ適用後の SAM の引数・戻り値型
シグネチャ
1. これらの情報を元にラムダのクラスを生成
• 引数をフィールドに格納するコンストラクタ
• 処理本体のメソッドを呼び出す SAM の実装
2. ラムダをインスタンス化する MethodHandle を生成、
CallSite に紐付け
- 33. 最終的に実行される処理
32
class Lambda {
static class Lambda$1 implements IntUnaryOperator {
private final int delta;
Lambda$1(int delta) { this.delta = delta; }
@Override public int applyAsInt(int n) {
return lambda$0(this.delta, n);
}
}
IntUnaryOperator adder(int delta) {
return <invokedynamic: new Lambda$1(delta)>;
}
private static int lambda$0(int delta, int n) {
return n + delta;
}
}
metaFactory
が生成
→ 結局、やってることは匿名クラスと(ほぼ)同じ!
- 35. なぜ invokedynamic?
• invokedynamic によるラムダ式の実行は、動
作としては匿名クラスと似たようなものでした
• なぜ、わざわざ invokedynamic を使うので
しょうか?
→(1) クラスファイルが少なくなるおかげで、起動が
速くなる、かもしれません
→(2) JVM が LambdaMetaFactory を独自に実装
することで、最適なインスタンス生成の方法を選
択できるようになります
34
- 36. (1) 起動時間
• Java SE 8 では、 Streams API の採用によって、
プログラム中で全面的にラムダ式が利用され
ることが想定されています(実態はどうあれ!)
• その際、ラムダ式を匿名クラス方式で実装す
ると、クラスファイルの数が飛躍的に増えるた
め、クラスローディングが遅くなってしまいます
• invokedynamic で、クラスを実行時に生成す
れば、起動時間が抑えられる、かもしれません
35
- 42. シングルトンインスタンス: 確認
41
$ cat >Lambda.java
import java.util.*;
public class Lambda {
static Comparator<String> comparator() {
return (x, y) -> x.length() - y.length();
}
public static void main(String[] args) {
System.out.println(comparator());
System.out.println(comparator());
System.out.println(comparator());
}
}
$ javac Lambda.java && java Lambda
Lambda$$Lambda$1@84aee7
Lambda$$Lambda$1@84aee7
Lambda$$Lambda$1@84aee7
- 45. ラムダの直列化
• 関数型インタフェースが Serializable を拡張
している場合、ラムダのインスタンスは直列化
できる必要があります
– 直列化(ラムダ→バイト列)、非直列化(バイト列→
ラムダ)した時、元のラムダと同じように機能する
必要がある
• ラムダのクラスが実行時に生成される時、どう
したら直列化・非直列化できるのでしょうか?
→ writeReplace / readResolve を使う
44
- 51. 参考文献
• Java SE 8 API Specification
– http://download.java.net/jdk8/docs/api/overview-summary.html
• JSR-335
– http://jcp.org/en/jsr/detail?id=335
• Brian Goetz “From Lambdas to Bytecode”
– http://wiki.jvmlangsummit.com/images/1/1e/2011_Goetz_Lambd
a.pdf
• 宮川 拓「Lambda 式に invokedynamic を使うのかもしれな
い話」
– http://d.hatena.ne.jp/miyakawa_taku/20120728/1343478485
50