-
Notifications
You must be signed in to change notification settings - Fork 712
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
Support lambdas and other generated classes in JITServer AOT cache #19549
Conversation
9989868
to
72e7329
Compare
Out of curiosity, what's the typical "class/class loader not found" rate for these runtime-generated classes during deserialization, or are those more or less gone now? Also, did you notice any increase in relocation failures? |
72e7329
to
0f3a265
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, it looks good. I have some questions:
- How do we handle class redefinition?
- Is it possible to avoid computing the deterministic hash at the server by passing it from client to server and then cache it?
- Could you please add a high level "design" in the description of this PR?
We should also have a way of disabling the entire mechanism in case there is something wrong with it in production. |
A thought: is it possible for a generated ROMClass to refer to other generated ROMClasses (by name) and therefore encounter issues when computing the deterministic hash? |
If I remember correctly, both "class not found" and deserialization/relocation failures are reduced significantly. I'll collect some statistics. |
Good point. The idea is to handle redefinition transparently as a class unload followed by a new class load, but looking at HookedByTheJIT.cpp I'm not actually sure if the class load hook is called for the new version of the class after redefinition.
Possible and not too difficult, but with some added complexity. When the client sends class info to the server, it can include the deterministic hash (which will need to be associated with the RAMClass directly, which is not currently done). I think this optimization should be implemented in a separate PR rather than here. |
For class redefinition we do not call the load/unload hooks, but there is a special hook. |
jenkins compile all jdk8 |
jenkins compile all jdk21 |
AcmeAir with JITServer AOT cache crashes during startup with the following stack trace:
|
0f3a265
to
618e27c
Compare
I think it is possible, although probably rare in practice. We miss out on AOT cache for such classes/methods, but there are no correctness issues (class lookups will simply fail). Supporting such scenario would add complexity and overhead (e.g. require checking whether any classes named in the constant pool are generated), and I'm not sure it's worth it. |
So we can have several lambda forms loaded by the same class loader and identical in content? |
Yes. The only difference is the class name suffix. But if I understand correctly, AOT currently doesn't support them anyway, so we don't need to handle lambda forms at this point. |
@tajila Since this PR has some small VM changes, please review or delegate the review for those parts. Thanks |
Redefined classes still get invalidated/purged from the deserializer caches including the generated classes map: openj9/runtime/compiler/control/HookedByTheJit.cpp Lines 2494 to 2495 in 9c173ce
So we miss out on AOT cache for the new class after redefinition, but I don't think this can result in any correctness issues.
Yes, but I think this in an edge case that should be handled in a separate PR.
I thought there were two different mechanisms, and only under one of them the RAMClass stays the same. If it does stay the same, we can recompute the hash, otherwise handle it as "class unload" ( |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The VM change looks good to me.
FYI @ThanHenderson |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than my comments, LGTM.
Some stats from AcmeAir + JMeter on JDK17: Before:
After:
|
618e27c
to
b25a67a
Compare
@mpirvu I still need to write a high level design description, but the code should be ready now. |
d96a40a
to
b920442
Compare
jenkins test sanity xlinuxjit,zlinuxjit jdk21 |
On zLinux the tests have actually passed. |
jenkins test sanity xlinuxjit jdk21 |
Tests on xlinux seem to have passed, though we are getting an infra failure late. For local tests I can run AcmeAirEE8 with Java17 outside containers.
|
This commit implements support for correctly and reliably serializing and deserializing/loading JITServer AOT cache methods that are defined by or refer to classes generated at runtime. The current list of recognized types of generated classes includes: - Lambda classes; - Dynamic proxy classes; - Generated reflection accessors. In each of these cases, the generated class name consists of a deterministic prefix followed by a suffix that can vary across JVMs. Generated classes are identified across client JVMs using the combination of: the class loader ID (name of its first loaded class), the deterministic class name prefix, and the deterministic ROMClass hash. The latter is computed after re-packing the ROMClass to truncate all the instances of the class name string down to the deterministic class name prefix. In order to lookup generated classes at the client JVM during AOT deserialization and load, we maintain a mapping between <loader, prefix, hash> and the RAMClass, which is populated in the class load JIT hook, and invalidated in the class unload and redefinition hooks. This mechanism can be disabled by passing the command line option `-Xjit:aotCacheDisableGeneratedClassSupport` to both the JITServer and the client JVMs. Signed-off-by: Alexey Khrabrov <khrabrov@cs.toronto.edu>
b920442
to
c45c588
Compare
The new force-pushed version only logs the error (including the class name, which would be useful in this assertion) instead of asserting. Only the first matching RAMClass is associated with a given ROMClass hash. |
jenkins test sanity zlinuxjit jdk21 |
Performance metrics seem to be stable after this PR. I will merge it. |
This PR implements support for correctly and reliably serializing and deserializing/loading JITServer AOT cache methods that are defined by or refer to classes generated at runtime.
The current list of recognized types of generated classes includes:
In each of these cases, the generated class name consists of a deterministic prefix followed by a suffix that can vary across JVMs.
Generated classes are identified across client JVMs using the combination of: the class loader ID (name of its first loaded class), the deterministic class name prefix, and the deterministic ROMClass hash. The latter is computed after re-packing the ROMClass to truncate all the instances of the class name string down to the deterministic class name prefix. In order to lookup generated classes at the client JVM during AOT deserialization and load, we maintain a mapping between <loader, prefix, hash> and the RAMClass, which is populated in the class load JIT hook, and invalidated in the class unload and redefinition hooks.
This mechanism can be disabled by passing the command line option
-Xjit:aotCacheDisableGeneratedClassSupport
to both the JITServer and the client JVMs.