Skip to content

Optimize reflection configuration in Native Image #3566

@loicottet

Description

@loicottet

Goal

Reduce image size and configuration size by limiting useless reflection capabilities being generated for methods never invoked through reflection and enabling reflection queries without configuration, while maintaining the option to fine-tune the reflection metadata being included for image size-sensitive applications.

Context

Reflection operation types

The Native Image implementation of Java’s reflection API currently doesn’t distinguish between two types of reflective operations:

  • Queries: only require metadata about class members (i.e. Class.getMethod(s))
  • Accesses: require the member to actually be present in the image (i.e. Method.invoke)

Shortcomings of the current implementation

This design decision leads to access capabilities being provided for members that are only queried but never accessed. This has an impact on the final image size mainly due to two factors:

  • Those methods are wrongly marked as reachable, which can lead to more unreachable code being included in the image;
  • A proxy class providing invocation capabilities is generated for all reflection-accessible methods but is never executed.

This only happens for methods and constructors, so the following proposal focuses solely on those.

Example

Given the following code (lightly edited for space):

class Test {
  public static void main() {
    callMethodsWithAnnotation(Test.class, Tag.class);
  }

  static void callMethodsWithAnnotation(Class<?> clazz, Class<?> annotationClazz) {
    for (Method m : clazz.getDeclaredMethods())
      if (m.getAnnotation(annotationClazz) != null)
        m.invoke(null);
  }

  @Tag
  void taggedMethod() { ... }

  void deadCode() { ... }
}

The current reflection configuration for this program is as follows:

[{
  "name":"Test",
  "allDeclaredMethods":true
}]

As a consequence, useless reflection invocation capabilities are created for the main, callMethodsWithAnnotation and, more importantly, deadCode, which could force the inclusion of an arbitrary amount of unreachable code into the image.

Evaluation of impact on image size and configuration

Protocol

We measured the impact of the following changes on various regular and microservice benchmarks:

  • Image size reduction when removing invocation capabilities for queried-only methods;
  • Image size increase when including metadata required for reflection queries in all reachable classes;
  • Configuration size reduction when including only invoked methods.

Results

Image impact

The results show that the reduction in image size gained through the elimination of reachability analysis false positives and the non-inclusion of useless proxy classes is larger than the size increase caused by full metadata inclusion in all evaluated cases, this difference being quite important on some microservice benchmarks, particularly those based on the Spring and Micronaut frameworks (up to 20%).

Config impact

The impact on configuration size is also positive, with the added bonus that the configuration is simplified by not needing the allDeclaredMethods and allPublicMethods fields. In most cases at most one of the methods included by these fields is actually invoked at runtime.

Proposal

Based on the results and to address the shortcomings presented above, we propose the following:

  • Only require actually invoked methods to be present in the reflection configuration;
  • Include metadata required for reflection queries by default in all classes;
  • Add a mode to enable fine tuning of the metadata being included in the image. This mode is enabled by the --configure-reflection-metadata Native Image option, and makes use of the following fields specifying methods being queried but not invoked:
    • queriedMethods, holding a list of methods;
    • the following boolean fields:
      • queryAllDeclaredMethods,
      • queryAllPublicMethods,
      • queryAllDeclaredConstructors,
      • queryAllPublicConstructors.

The Native Image agent will be updated to output valid configuration files with or without (by default) the fine-tuning fields. Fine-tuning can be enabled by specifying the track-reflection-metadata option. Example:
java -agentlib:native-image-agent=track-reflection-metadata ...

Example

Proposed new configuration for the example presented above:

[{
  "name":"Test",
  "methods":[{"name":"taggedMethod", "parameterTypes":[]}]
}]

Proposed configuration for the same code in optimized mode:

[{
  "name":"Test",
  "queryAllDeclaredMethods":true,
  "methods":[{"name":"taggedMethod", "parameterTypes":[]}]
}]

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions