JLink-Time Optimization

Stories about Jlink, jigsaw and java.lang.invoke

Claes Redestad
Java SE Performance Team, Oracle

Safe harbor statement

The following is intended to outline our general product direction. It is intended for information purposes only, and may not be incorporated into any contract. It is not a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. The development, release, and timing of any features or functionality described for Oracle’s products remains at the sole discretion of Oracle.

Jigsaw

  • Organizes code into modules
  • ... which are built into self-contained bundles: jmod
  • ... which are linked together into a runtime image: jlink

JEP-282: "Link time is an opportunity to do whole-world optimizations that are otherwise difficult at compile time or costly at runtime."

Jlink plugins

  • Image customization enabled using a set of predefined plugins:
    
      jlink --list-plugins 
    	
  • As per the JEP, the plugin API is strictly experimental[1]
  • Plugin behavior controlled via jlink command line flags
    
      jlink ...
        --strip-debug
        --compress=2
        --include-locales=en
        --class-for-name
    	

[1] Latest API breaking change: 2016-07-28

Class.forName

Commonly used with constant strings in places where the class looked up is not visible at compile time but assumed available at link time

Optimization: Replace with static references when valid:


  jlink --class-for-name
	


  public final class ClassForNamePlugin implements Plugin {

    public ResourcePool transform(ResourcePool in,
                                  ResourcePoolBuilder out) {
      in.entries()
        .forEach(resource -> out.add(transform(resource)));
      return out.build();
    }

    ResourcePoolEntry transform(ResourcePoolEntry resource) {
      /* use ASM to rewrite classes matching the pattern */
    }

... some API changes to be expected

Optimizing jigsaw startup

Observation: Significant time spent parsing and validating module-info.class classes for each module in the system image itself

Solution: Implement a jlink plugin to generate a pre-validated representation of the system module graph, jdk.internal.module.SystemModules

Also, since this is being done ahead-of-time we can profitably do various global optimizations, such as de-duplicating identical sets of export targets, that would be contraproductive if done at runtime

How big improvement?

time java -version

Reduces memory use by a sizable amount, too!
(Actually, this plugin is always on, since nothing good would come from disabling it)

Indy initialization

Observation: First usage of java.lang.invoke has a substantial cost: 100+ classes loaded, many of which are generated dynamically.

Solutions?

  • Cry
  • Make it lazier: JDK-8142334
    (moves ~15ms off initial initialization)
  • Apply jlink?
java.lang.invoke at a glance

Most BMHs are generated in runtime; LFs are currently compiled to bytecode when initialized

Generate more ahead of time

JDK-8152641 introduce a plugin to generate BoundMethodHandles ahead of time:


    jlink --generate-jli-classes=bmh::bmh-species=LL,L3,...

  • Default based on ISC (JEP-280) needs
  • Reduces initialization overhead of first ISC usage by ~15%
Can we do even better?

DMH pre-generation prototyped:

    jlink --generate-jli-classes=bmh::bmh-species=LL,L3\
        :invokeStatic=_V,L_V,LL_V\
        :invokeSpecial=_V,L_V,LL_V\
        :invokeVirtual=_V,L_V

All DMHs are generated into a single class and looked up speculatively, while dynamically generated DMHs are generated into an individual class.

One size does not fit all: default strategy likely needs to be conservatively tuned; specific deployments could choose to generate all DMHs needed into their custom runtime image

With great power comes technical debt
  • Configuring --generate-jli-classes is cumbersome, and requires intimate knowledge of java.lang.invoke internals
  • Plugin is currently using core reflection (and setAccessible) to access internal hooks in j.l.invoke
TODO
  • Generalize to pregenerate all LFs, when possible, ultimately support replacing lambdas with generated code at link-time
  • Refactor j.l.invoke internals to encapsulated package
  • Isolated methods
  • Simplify unwieldy configuration
    • jlink configuration file
    • Tool to generate configuration from profiled application

Going further

While there are things jlink probably shouldn't do by default, there are many opportunities to do speculative, intrusive or application specific alterations

  • Prune unused, deprecated or simply unwanted code and resources (asserts, serialization support)
  • Ahead-of-time instrumentation (agents)
  • Got XML? Digest configuration files into more efficient internal representations (serialized object, bytecode..)

Done

Questions?

Appendix: Other (existing) optimizations

--order-resources: arrange layout of resources in the runtime image based on a list of classes retrieved via profiling etc. Improves cold startup.

--include-locales: Selectively include the locales you need to create a smaller image

--strip-debug: Strip debug symbols to create a smaller image

Copyright © 2016, Oracle and/or it's affiliates. All rights reserved