Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize reflection configuration in Native Image #3566

Closed
loicottet opened this issue Jul 9, 2021 · 2 comments
Closed

Optimize reflection configuration in Native Image #3566

loicottet opened this issue Jul 9, 2021 · 2 comments
Assignees
Milestone

Comments

@loicottet
Copy link
Member

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":[]}]
}]
@loicottet loicottet self-assigned this Jul 9, 2021
@vjovanov vjovanov added this to the 21.3 milestone Jul 9, 2021
@vjovanov vjovanov changed the title Native Image reflection configuration optimization Optimize reflection configuration in Native Image Jul 10, 2021
@zakkak
Copy link
Collaborator

zakkak commented Aug 17, 2021

Related WIP PR #3637

@vjovanov
Copy link
Member

Fixed in 21.3.

sdeleuze added a commit to sdeleuze/spring-native that referenced this issue Oct 26, 2021
This commit adds supports for reflective query introduced
via oracle/graal#3566 which allows to reduce the memory footprint
when only access to reflective metadata is required, without reflective
invocation.

They can be specified via @typehint using flags like with
`@TypeHint(types = Bar.class, access = AccessBits.QUERY_DECLARED_METHODS))`
or by listing explicitly the methods like with
`@TypeHint(types = Foo.class, queriedMethods = @MethodHint(name = "toto", parameterTypes = String.class))`.

Closes spring-atticgh-1156
sdeleuze added a commit to sdeleuze/spring-native that referenced this issue Oct 26, 2021
This commit adds supports for reflective query introduced
via oracle/graal#3566 which allows to reduce the memory footprint
when only access to reflective metadata is required, without reflective
invocation.

They can be specified via @typehint using flags like with
`@TypeHint(types = Bar.class, access = AccessBits.QUERY_DECLARED_METHODS))`
or by listing explicitly the methods like with
`@TypeHint(types = Foo.class, queriedMethods = @MethodHint(name = "toto", parameterTypes = String.class))`.

Closes spring-atticgh-1156
sdeleuze added a commit to sdeleuze/spring-native that referenced this issue Oct 26, 2021
This commit adds supports for reflective query introduced
via oracle/graal#3566 which allows to reduce the memory footprint
when only access to reflective metadata is required, without reflective
invocation.

They can be specified via @typehint using flags like with
`@TypeHint(types = Bar.class, access = AccessBits.QUERY_DECLARED_METHODS))`
or by listing explicitly the methods like with
`@TypeHint(types = Foo.class, queriedMethods = @MethodHint(name = "toto", parameterTypes = String.class))`.

Closes spring-atticgh-1156
sdeleuze added a commit to sdeleuze/spring-native that referenced this issue Oct 26, 2021
This commit adds supports for reflective query introduced
via oracle/graal#3566 which allows to reduce the memory footprint
when only access to reflective metadata is required, without reflective
invocation.

They can be specified via @typehint using flags like with
`@TypeHint(types = Bar.class, access = AccessBits.QUERY_DECLARED_METHODS))`
or by listing explicitly the methods like with
`@TypeHint(types = Foo.class, queriedMethods = @MethodHint(name = "toto", parameterTypes = String.class))`.

See spring-atticgh-1156
sdeleuze added a commit to sdeleuze/spring-native that referenced this issue Oct 26, 2021
This commit adds supports for reflective query introduced
via oracle/graal#3566 which allows to reduce the memory footprint
when only access to reflective metadata is required, without reflective
invocation.

They can be specified via @typehint using flags like with
`@TypeHint(types = Bar.class, access = AccessBits.QUERY_DECLARED_METHODS))`
or by listing explicitly the methods like with
`@TypeHint(types = Foo.class, queriedMethods = @MethodHint(name = "toto", parameterTypes = String.class))`.

See spring-atticgh-1156
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants