Goal
Improve start-up time by storing pre-generated LambdaForm handlers in an application-specific CDS archive.
Background
In JDK-8086045 Improve the java.lang.invoke first initialization costs, the JDK can reduce the number of dynamically generated LambdaForm classes. Here's an example:
# Do this in your JDK build directory. # The exploded JDK build: $ jdk/bin/javac -J-verbose ~/tmp/HelloWorld.java | grep LambdaForm | grep __JVM_LookupDefineClass__ [0.142s][info][class,load] java.lang.invoke.LambdaForm$MH/0x0000000800066c40 source: __JVM_LookupDefineClass__ [0.143s][info][class,load] java.lang.invoke.LambdaForm$DMH/0x0000000800066040 source: __JVM_LookupDefineClass__ [0.143s][info][class,load] java.lang.invoke.LambdaForm$MH/0x0000000800066440 source: __JVM_LookupDefineClass__ [snip] $ jdk/bin/javac -J-verbose ~/tmp/HelloWorld.java | grep LambdaForm | grep __JVM_LookupDefineClass__ | wc 102 408 11400 # The final JDK image: $ images/jdk/bin/javac -J-verbose ~/tmp/HelloWorld.java | grep LambdaForm | grep __JVM_LookupDefineClass__ | wc 34 136 3800
Here's the difference between the exploded build and the final image:
$ jdk/bin/javap 'java.lang.invoke.DirectMethodHandle$Holder' Compiled from "DirectMethodHandle.java" final class java.lang.invoke.DirectMethodHandle$Holder { final java.lang.invoke.DirectMethodHandle this$0; java.lang.invoke.DirectMethodHandle$Holder(java.lang.invoke.DirectMethodHandle); } $ images/jdk/bin/javap 'java.lang.invoke.DirectMethodHandle$Holder' Compiled from "DirectMethodHandle$Holder" final class java.lang.invoke.DirectMethodHandle$Holder { static int invokeInterface(java.lang.Object, java.lang.Object, java.lang.Object); static java.lang.Object invokeSpecial(java.lang.Object, java.lang.Object, java.lang.Object, int); static java.lang.Object invokeSpecial(java.lang.Object, java.lang.Object, java.lang.Object, long); [~150 more lines ...] }
The JDK image's version of the java.lang.invoke.DirectMethodHandle$Holder class is generated here in GenerateJLIClassesPlugin.java. This plugin is executed when we generate the file images/jdk/lib/modules.
private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder"; ... byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes( DIRECT_HOLDER, directMethodTypes, dmhTypes);
The input of generateDirectMethodHandleHolderClassBytes comes from the default_jli_trace.txt file that is generated in GenerateLinkOptData.gmk:
$(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.raw.2 \ -XX:SharedClassListFile=$@.interim -XX:SharedArchiveFile=$@.jsa \ -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true \ -Duser.language=en -Duser.country=US \ --module-path $(SUPPORT_OUTPUTDIR)/classlist.jar \ -cp $(SUPPORT_OUTPUTDIR)/classlist.jar \ build.tools.classlist.HelloClasslist \ 2> $(LINK_OPT_DIR)/stderr > $(JLI_TRACE_FILE)
Here's how roughly the contents of default_jli_trace.txt correspond to the generated methods in DirectMethodHandle$Holder
$ grep invokeSpecialIFC ./support/link_opt/default_jli_trace.txt [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecialIFC L3I_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecialIFC LLI_I (fail) $ images/jdk/bin/javap 'java.lang.invoke.DirectMethodHandle$Holder' | grep invokeSpecialIFC static int invokeSpecialIFC(java.lang.Object, java.lang.Object, java.lang.Object, int); static int invokeSpecialIFC(java.lang.Object, java.lang.Object, int);
The methods in DirectMethodHandle$Holder are used in InvokerBytecodeGenerator.java when compiling for LambdaForms:
private static MemberName lookupPregenerated(LambdaForm form, MethodType invokerType) { if (form.customized != null) { // No pre-generated version for customized LF return null; } String name = form.kind.methodName; switch (form.kind) { [......] case DIRECT_INVOKE_INTERFACE: // fall-through case DIRECT_INVOKE_SPECIAL: // fall-through case DIRECT_INVOKE_SPECIAL_IFC: // fall-through case DIRECT_INVOKE_STATIC: // fall-through case DIRECT_INVOKE_STATIC_INIT: // fall-through case DIRECT_INVOKE_VIRTUAL: return resolveFrom(name, invokerType, DirectMethodHandle.Holder.class); } return null;
Estimated Benefits
The expected benefits will be application specific. Here's an experiment with Javac (I modified the JDK makefile to invoke com.sun.tools.javac.Main while generating default_jli_trace.txt).
Results of " perf stat -r 50 bin/javac -J-Xshare:on -J-XX:SharedArchiveFile=javac2.jsa Bench_HelloWorld.java " 1: 2239149872 2203293903 (-35855969) ---- 375.580 367.190 ( -8.390) ---- 2: 2245304165 2198900711 (-46403454) ----- 375.350 366.910 ( -8.440) ---- 3: 2242558895 2205167765 (-37391130) ---- 376.720 366.480 (-10.240) ----- 4: 2246851428 2198990987 (-47860441) ----- 375.119 367.010 ( -8.109) ---- 5: 2238549008 2202480450 (-36068558) ---- 374.100 367.600 ( -6.500) --- 6: 2240816859 2200725384 (-40091475) ---- 375.743 366.880 ( -8.863) ---- 7: 2243272464 2199639926 (-43632538) ----- 374.296 365.990 ( -8.306) ---- 8: 2239650882 2203211291 (-36439591) ---- 375.315 365.520 ( -9.795) ----- 9: 2242434935 2202380291 (-40054644) ---- 376.052 367.305 ( -8.747) ---- 10: 2238725993 2201342398 (-37383595) ---- 375.478 366.743 ( -8.735) ---- ============================================================ 2241729810 2201612439 (-40117371) ---- 375.375 366.762 ( -8.612) ---- instr delta = -40117371 -1.7896% time delta = -8.612 ms -2.2943%
Why do it in CDS
- We cannot store all possible LambdaForm handlers in the standard JDK image – we cannot enumerate all possible forms that may be used by all possible apps.
- We could also do this as part of jlink, when creating a custom JDK image. However, some users may not want to create a new JDK image.
- Also, there's currently no work-flow to include profiling data when building a custom JDK image (although this can be changed, probably as part of Project Leyden)
- CDS dynamic dump can be used without generating profiling data in a separate step, so the usability is better.
CDS Design
Overally, we want to modify lookupPregenerated() to something like:
case xxxxxx: return resolveFrom(name, invokerType, directMethodHandleHolders); Class[] directMethodHandleHolders = { DirectMethodHandle.Holder.class, // the one in jdk/lib/modules Class.forName("java/lang/invoke/DirectMethodHandle$CDSHolder1"), // if available, from CDS static archive Class.forName("java/lang/invoke/DirectMethodHandle$CDSHolder2"), // if available, from CDS static archive };
During CDS dump (static or dynamic), we will generate the CDSHolder1 and CDSHolder2 classes according to the program's execution profile.
Static Dump
During the trial run (with -XX:DumpLoadedClassList=classlist), we can save the list of LambdaForm handlers using a special syntax like:
# regular class specifier java/lang/Object # LambdaForm specifier (syntax TBD, but probably following the output of # -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecialIFC L3I_I [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecialIFC LLI_I .... #The output of -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true (javac HelloWorld.java): [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L7_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LL_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L8_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder invokeExact_MT L8_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L3_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L4_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder getReference LL_L (success) [LF_RESOLVE] java.lang.invoke.LambdaForm$Holder identity_L LL_L (success) [LF_RESOLVE] java.lang.invoke.LambdaForm$Holder zero_L L_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LL_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod L_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L3_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial LL_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L3_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod LL_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual LL_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial L_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeInterface L3_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LL_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LL_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LI_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial L3_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L4_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod L3_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial L4_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L5_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod L4_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L3_V (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L4IIL_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial L4II_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L5II_L (fail) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod L3IIL_L (fail) [LF_RESOLVE] java.lang.invoke.DelegatingMethodHandle$Holder delegate L3_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L4_V (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L3_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeInterface L3_V (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L6_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L7_L (success) [LF_RESOLVE] java.lang.invoke.DelegatingMethodHandle$Holder delegate L6_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder invokeExact_MT L7_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LLJ_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L3J_L (success) [LF_RESOLVE] java.lang.invoke.DelegatingMethodHandle$Holder reinvoke_L LLJ_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LJLIL_J (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LLJLIL_J (success) [LF_RESOLVE] java.lang.invoke.DelegatingMethodHandle$Holder reinvoke_L LJLIL_J (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L5_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LLJLI_J (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LJ_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LLJ_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LJI_J (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LLJI_J (success) [LF_RESOLVE] java.lang.invoke.DelegatingMethodHandle$Holder reinvoke_L LJI_J (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L4J_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder getLong LL_J (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LLI_J (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LLI_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod IL_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecialIFC LLI_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStaticInit LL_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual LL_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStaticInit LL_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial L5_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L6_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod L5_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial L6_L (fail) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod L6_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L3_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeInterface LL_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStaticInit L_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LII_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial LI_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LI3_I (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder newInvokeSpecial LII_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial LLII_L (success) [LF_RESOLVE] java.lang.invoke.Invokers$Holder linkToTargetMethod IIL_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L3_V (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L4_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L3II_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual LLI_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L3II_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L6_V (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L5_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LL_V (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LLII_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeSpecial L3_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeInterface LL_L (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L_V (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic LI_L (success) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L4_I (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual LL_V (fail) [LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeStatic L3_V (fail) total is 93. But with LambdaForm only 2: [LF_RESOLVE] java.lang.invoke.LambdaForm$Holder identity_L LL_L (success) [LF_RESOLVE] java.lang.invoke.LambdaForm$Holder zero_L L_L (success) # for LambdaForm: # javac -J-verbose HelloWorld.java | grep grep LambdaForm | grep __JVM_LookupDefineClass__ # total is 36.
We collect all the LF_RESOLVE lines when parsing the classlist. At the end of the static dump, we invoke generateDirectMethodHandleHolderClassBytes to generate the DirectMethodHandle$CDSHolder1 class (as well as other holder classes that are currently generated by GenerateJLIClassesPlugin.java.)
Details TBD ....
Dynamic Dump
TBD.