Is there any way to make the JVM cache the optimized native code it generates to a file, to improve the performance of future runs?
Ahead-of-Time (AOT) compilation is available since JDK 9. See JEP 295.
Note: this is not the same as GraalVM Native Image.
Caching of JIT compiled code is problematic for several reasons.
HotSpot compilers heavily rely on speculative optimizations. These optimizations are based on certain runtime conditions that may not be always the same from one run to another.
JIT compiled code may refer to the particular objects and classes, it may rely on the constants which values are known in runtime only, it may have memory addresses inlined directly into the instruction stream. This makes the cached code impossible for direct reuse. The problem can be solved by an extra level of indirection, which will impact the performance.
That's why several JVM vendors took another approach: to cache runtime profile data instead of the final code. The profile data collected from the previous run can then be used to recompile the hottest methods as soon as possible (once all the preconditions are met) without need to run methods in the interpreter to collect the profile again.
There are at least two known solutions:
Zing Virtual Machine also has Compile Stashing technology to reuse the compiled code itself. It can be used together with ReadyNow!.
There is a JEP draft to add JWarmup support in OpenJDK.
Here is a nice blog post about AOT Compilation in HotSpot, JIT Caching and related technologies.