Resolved methods are wrappers around Java methods which exist for the duration of a single compilation. They collect and manage information about a method and related classes. Resolved methods are some of the most commonly used entitites by the JIT compiler, thus it is important to handle them correctly and efficiently on JITServer.
For non-JITServer, we mostly care about 2 classes representing a resolved method - TR_ResolvedJ9Method
and TR_ResolvedRelocatableJ9Method
.
The former represents resolved methods in a regular compilation, and the latter in an AOT compilation.
On the server, we implement two new classes to represent resolved methods - TR_ResolvedJ9JITServerMethod
and TR_ResolvedRelocatableJ9JITServerMethod
.
Same as for non-JITServer, the former is for regular compilations and the latter is for AOT. TR_ResolvedJ9JITServerMethod
is extended from TR_ResolvedJ9Method
and
TR_ResolvedRelocatableJ9JITServerMethod
is extended from TR_ResolvedJ9JITServerMethod
.
Upon instantiation of a server-side resolved method, a resolved method is also created on the client that represents the same Java method. We call that client-side copy "mirror".
Instantiation happens either directly via the createResolvedMethod
family, or indirectly using one of multiple getResolvedXXXMethod
methods, which perform operations to locate a method of interest and then create the corresponding ResolvedMethod
.
Some answers to resolved method queries are cached at the server to avoid sending remote messages.
All of this information is sent during method instantiation, inside TR_ResolvedJ9JITServerMethodInfo
.
The cached values either won't change during the lifetime of a resolved method (i.e. constant pool pointer, J9 class pointer)
or having incorrect value cached for the duration of one compilation does not have impact on correctness or performance (i.e. whether a method is interpreted).
Resolved methods could also be used as more general per-compilation caches, containing information that does not directly describe the resolved method itself.
Since resolved methods are destroyed at the end of compilation, this would be functionally correct.
However, such usage is discouraged because TR::CompilationInfoPerThreadRemote
is a better place for such caches. Placing caches there makes them more visible
and it makes more sense to store information not related to resolved methods outside of them.
Since resolved methods are so frequently created and used, the number of remote messages associated with them is normally a large portion of total messages. Therefore, minimizing the number of requests for resolved method creation is important for improving CPU consumption.
There are two main ways we optimize resolved method creation:
- Caching all newly created resolved methods in a per-compilation cache on the server. We use
TR_ResolvedMethodInfoCache
and put it inTR::CompilationInfoPerThreadRemote
. Whenever a resolved method needs to be created, server will first check the cache and recreate the resolved method from there, if possible. - Prefetching multiple resolved methods before they are first requested. There are parts of the compiler that request many resolved methods in a loop.
For example, ILGen and inliner will walk through method bytecodes and create a resolved method for every method call bytecode.
We use
TR_ResolvedJ9JITServerMethod::cacheResolvedMethodsCallees
to do a preliminary walk and cache all required methods in one remote call. Techniques like these do not reduce the total number of resolved methods created or data transferred through the network but it reduces the total number of remote calls, which still has a positive impact on CPU consumption.
Despite all these optimizations, resolved methods are still responsible for most of the remote messages. This is due to the fact that resolved methods only exist for the duration of a single compilation so any type of persistent caching is not available to us.