JEP 309: Dynamic Class-File Constantsというものがあります。これはConstant Dynamic(condy)と呼ばれるものです。
http://openjdk.java.net/jeps/309
Constant Dynamic、つまり動的な定数です。Javaでは今まで(10まで)プリミティブとリテラルを持つStringだけConstant Poolに格納できました。condyでその他のものもConstant Poolで扱えるようになります。
さらに、condyでは定数値の初期化を遅延します。今までコンパイル時に値が確定しているものだけが対象でしたが、それを拡大します。
Invokedynamicとの類似性
Invokedynamic(indy)というものがJava 7からあります。JVMで動的な呼び出しを実現するものです。コードの実行時にInvokedynamicに遭遇すると、初回のみブートストラップメソッドを呼び出してCallSiteオブジェクトを生成し、以降同じ内容のinvokedynamicはそのCallSiteオブジェクトを使います。
CallSiteは初回のinvokedynamicの実行時まで生成が遅延されている、と言えます。
Invokedynamicはメソッド呼び出しを対象としていました。condyはこの仕組みを定数に対して適用したものと考えてよいです。その定数の初回使用時にブートストラップメソッドを使い定数の値を確定させます。ブートストラップメソッドからはあらゆるオブジェクトを返せるので、こうしてConstant PoolでプリミティブとString以外を扱えるようになるのです(nullもです)。
参考セッション動画
JVMLS 2018でOpenJ9の開発者Dan HeidingaさんとPaul Sandozさんによるcondyのセッションがありました。私は現地にて聴きました。録画も公開されています。
Java 11で試す
condyの実装はすでに終わっています。この記事の時点ではJava 11はEarly-Accessビルドですが、condyに対応しています。
$ java -version openjdk version "11" 2018-09-25 OpenJDK Runtime Environment 18.9 (build 11+28) OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
condy自体は実装が完了しているとは言え、現時点ではコンパイラがcondyを生成するJavaのコードはありません。JVM側はcondy対応が完了しcondyを実行できるが、Java側でまだ使っていないということです。indyリリース時と同じ感じです。
そのため、ここではバイトコード操作でcondyを使うクラスを出力します。バイトコード操作ライブラリとしてByte Buddyを使います。Byte Buddyは1.8.16以降Java 11に対応しており、condyやnestmateといった機能に対応しています。このことについてはInfoQ.comの記事があります。翻訳者は私です。
public static class TargetClass { public Object targetMethod() { return null; } }
TargetClassはバイトコード操作のために作っただけです。Byte BuddyにはTargetClassのサブクラスを作成させ、そのサブクラスにcondyを入れます。
public static class CondyClass { public static CondyClass make() { return new CondyClass(); } }
CondyClassは定数の値となるクラスです。condyでこのmake()メソッドの戻り値が定数値となります。ここでは単純にCondyClassオブジェクトにしました。
public class CondySample { public static void main(String[] args) throws Exception { DynamicType.Unloaded<TargetClass> unloaded = new ByteBuddy() .subclass(TargetClass.class) .method(isDeclaredBy(TargetClass.class)) .intercept(FixedValue.value(JavaConstant.Dynamic.ofInvocation(CondyClass.class.getMethod("make")))) .make(); unloaded .saveIn(Paths.get("./temp").toFile()); TargetClass targetClass = unloaded .load(TargetClass.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded() .getDeclaredConstructor() .newInstance(); System.out.println(targetClass.targetMethod()); } }
main()メソッドの前半でTargetClassのサブクラスを作成し、メソッド呼び出しをインターセプトして固定値としてcondyで生成する定数を返すよう変更します。Byte Buddyではcondy関連はnet.bytebuddy.utility.JavaConstant.Dynamic
から利用します。tempディレクトリに作成したクラスのクラスファイルを出力します。
/** * Represents a dynamically resolved constant pool entry of a class file. This feature is supported for class files in version 11 and newer. */ class Dynamic implements JavaConstant { }
後半は、作成したTargetClassのサブクラスをクラスローダにロードさせ、ClassクラスからnewInstance()でインスタンス化し、targetMethod()
メソッドを呼び出します。もともとのTargetClassではreturn null;
していましたが、サブクラスではcondyで作成した定数値を返すようオーバーライドしています。
これを実行します。エラーになります。
Exception in thread "main" java.lang.UnsupportedOperationException: This feature requires ASM7 at net.bytebuddy.jar.asm.MethodVisitor.visitLdcInsn(MethodVisitor.java:547)
Java 11対応はまだ実験的機能のようで、オプション-Dnet.bytebuddy.experimental=true
をつけて実行する必要があります。
com.sakatakoichi.CondySample$CondyClass@4cc451f2
targetMethod()
を呼び出した結果CondyClassクラスのインスタンスが返ってきており、それを出力しています。
バイトコード
実行しましたが、本当にcondyは利用されているのでしょうか?バイトコードを見てみましょう。tempディレクトリに保存しました。temp/com/sakatakoichi/CondySample$TargetClass$ByteBuddy$1a3Y6WFv.class
といったByte Buddyがランダムに命名したクラスが出力されているはずです。javap -v
します。
Constant pool: ... #23 = Dynamic #0:#22 // #0:invoke:Lcom/sakatakoichi/CondySample$CondyClass;
condyでConstant PoolにDynamicというタイプが追加されています。CondyClassであるとコメントから読み取れます。
BootstrapMethods: 0: #20 REF_invokeStatic java/lang/invoke/ConstantBootstraps.invoke:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object; Method arguments: #13 REF_invokeStatic com/sakatakoichi/CondySample$CondyClass.make:()Lcom/sakatakoichi/CondySample$CondyClass;
BootstrapMethods属性でcondyのConstantBootstraps.invoke
を設定しているのがわかります。その引数はCondyClass.make()のメソッドハンドルです。
#13 = MethodHandle 6:#12 // REF_invokeStatic com/sakatakoichi/CondySample$CondyClass.make:()Lcom/sakatakoichi/CondySample$CondyClass;
condyでjava.lang.invoke.ConstantBootstraps
クラスが追加されています。さまざまなメソッドがありますが、今回呼び出しているメソッドはこちらです。
/** * Returns the result of invoking a method handle with the provided * arguments. ... */ public static Object invoke(MethodHandles.Lookup lookup, String name, Class<?> type, MethodHandle handle, Object... args) throws Throwable { ... if (type != handle.type().returnType()) { // Adjust the return type of the handle to be invoked while // preserving variable arity if present handle = handle.asType(handle.type().changeReturnType(type)). withVarargs(handle.isVarargsCollector()); } return handle.invokeWithArguments(args); }
最終的には、引数で渡したメソッドハンドルを呼び出し、その結果を返しています。
まとめ
Java 11で導入されるJEP 309 Constant Dynamic(condy)を試しました。現在indyを使っているラムダ式も、今後non-capturingなものはcondyを使うように変更するといったことも検討されているようです。