{"id":227111,"date":"2019-10-29T16:47:32","date_gmt":"2019-10-29T23:47:32","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/java\/?p=227111"},"modified":"2020-03-18T11:56:42","modified_gmt":"2020-03-18T18:56:42","slug":"aot-compilation-in-hotspot-introduction","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/java\/aot-compilation-in-hotspot-introduction\/","title":{"rendered":"AOT Compilation in HotSpot: Introduction"},"content":{"rendered":"<div class=\"mceTemp\"><\/div>\n<p><em>This blog post is not about SubstrateVM nor GraalVM but focuses on the <span class=\"lang:default decode:true crayon-inline\">jaotc<\/span> <\/em><em>AOT compiler in HotSpot.<\/em><\/p>\n<h2>Introduction<\/h2>\n<p>In this blog post, we are going to focus on the Ahead-Of-Time (AOT) Compilation that was introduced in Java 9 (<a href=\"https:\/\/openjdk.java.net\/jeps\/295\">https:\/\/openjdk.java.net\/jeps\/295<\/a>) with the addition of the <span class=\"lang:default decode:true crayon-inline\">jaotc<\/span> command-line utility. This AOT compiler is based on the work done in Graal JIT.<\/p>\n<p>We are going to explore some of the tradeoffs that the AOT compiler needs to take, and how the generated code fits in the Tiered Compilation (TC) pipeline. Then, we will go through a simple example, showing how to use the <span class=\"lang:default decode:true crayon-inline\">jaotc<\/span>\u00a0command-line utility. Finally, we are going to explore some alternatives to the AOT compiler like JIT at Startup, JIT caching, and Distributed JIT.<\/p>\n<h2>AOT Compilation in HotSpot<\/h2>\n<p>An AOT compiler&#8217;s primary capability is to generate machine code for an application without having to run the application, allowing a future run of the application to pick the generated code. Similarly, to C1 and C2, <span class=\"lang:default decode:true crayon-inline\">jaotc<\/span>\u00a0compiles Java bytecode to native code.<\/p>\n<p>The primary motivator behind using AOT in Java is to bypass the interpreter. It is generally faster for the machine to execute machine code than it is to execute the code via the bytecode interpreter. In many cases, it is a definite advantage, especially for code that needs to be executed even just a few times.<\/p>\n<h3>Tradeoffs of generated code<\/h3>\n<p>An AOT compiler cannot make the same class of assumptions as a JIT compiler. The AOT compiler doesn\u2019t have access to as much information as the JIT compiler does because the process generating and executing the application are not the same.<\/p>\n<p>For example, AOT compilers are required to generate Position Independent Code (PIC) to produce shared libraries. That is because there is no way to know ahead of execution where in memory the code is loaded, blocking any assumption the AOT compiler can make on the location (relative or absolute) of a symbol; this prevents the AOT compiler from referencing the address of any symbol directly. So, whenever a symbol (such as functions and constants) is accessed, it requires the AOT compiler to generate an indirection, with the resolution happening on first access to the symbol.<\/p>\n<p>On the other hand, a JIT compiler can take the address in memory of a symbol and embed it directly in the code. It works because the JIT compiler can assume the code to have a shorter lifetime than the symbol: the code generation happens after the symbol initialization (or at least the code generation initializes the symbol), and the shutdown of the process triggers the destruction of both the code and the symbol.<\/p>\n<p>Another example is <span class=\"lang:default decode:true crayon-inline \">final static<\/span> variables. A JIT compiler can make certain assumptions allowing it to generate code based on the value of the variable. But because an AOT compiler cannot know the value of the variable <em>before<\/em> the initialization of the variable \u2013 which only happens at the execution of the code \u2013 it can\u2019t make the same assumptions. That can lead to missed optimizations opportunities like dead-code elimination or inlining.<\/p>\n<p>Finally, the OS and architecture on which you execute the code and on which you generate the code are required to be the same. For example, if you want to execute the code on Windows, you cannot generate the code on Linux or macOS but only on Windows. That is because the <span class=\"lang:default decode:true crayon-inline \">jaotc<\/span> does not support cross-compilation.<\/p>\n<h3>Integration with the Tiered Compilation pipeline<\/h3>\n<p>Introduced in Java 7, Tiered Compilation (TC) goal is to have fast startup time and fast steady-state throughput. The implementation consists of a pipeline of multiple tiers of code generation. The three main components of this pipeline are the interpreter, the C1 compiler, and the C2 compiler. It replaced the <span class=\"lang:default decode:true crayon-inline \">-client<\/span> and <span class=\"lang:default decode:true crayon-inline \">-server<\/span> command-line parameters available in previous versions of Java.<\/p>\n<p>As the method goes through the different tiers, each tier gathers information about the method execution. This information is called Profiling Data (PD). The C2 compiler uses this PD to make certain assumptions such as what code paths are cold\/warm\/hot, and what types are used at any call sites. It can then generate code better suited for the specific context that it is currently executing in.<\/p>\n<p>The five tiers of code generation are:<\/p>\n<ul>\n<li><strong>none (0):<\/strong> Interpreter gathering full PD<\/li>\n<li><strong>simple (1):<\/strong> C1 compiler with no profiling<\/li>\n<li><strong>limited profile (2):<\/strong> C1 compiler with light profiling gathering some PD<\/li>\n<li><strong>full profile (3):<\/strong> C1 compiler with full profiling gathering full PD<\/li>\n<li><strong>full optimization (4):<\/strong> C2 compiler with no profiling<\/li>\n<\/ul>\n<p>With <span class=\"lang:default decode:true crayon-inline\">jaotc<\/span>, you have the option to generate code with or without support for TC. Enabling TC generates slightly slower code due to the profiling overhead. Disabling TC blocks the use of the TC pipeline leading to slower steady-state throughput.<\/p>\n<p>Figure 1 and Figure 2 show the flow in the TC pipeline if you use AOT or not.<\/p>\n<p>&nbsp;<\/p>\n<p><figure id=\"attachment_227131\" aria-labelledby=\"figcaption_attachment_227131\" class=\"wp-caption aligncenter\" ><img decoding=\"async\" class=\"wp-image-227131 size-full\" src=\"http:\/\/devblogs.microsoft.com\/java\/wp-content\/uploads\/sites\/51\/2019\/10\/TC-without-AOT.png\" alt=\"Tiered Compilation pipeline without AOT\" width=\"657\" height=\"600\" srcset=\"https:\/\/devblogs.microsoft.com\/java\/wp-content\/uploads\/sites\/51\/2019\/10\/TC-without-AOT.png 657w, https:\/\/devblogs.microsoft.com\/java\/wp-content\/uploads\/sites\/51\/2019\/10\/TC-without-AOT-300x274.png 300w\" sizes=\"(max-width: 657px) 100vw, 657px\" \/><figcaption id=\"figcaption_attachment_227131\" class=\"wp-caption-text\">Figure 1: Tiered Compilation pipeline without AOT<\/figcaption><\/figure><\/p>\n<p>&nbsp;<\/p>\n<p><figure id=\"attachment_227132\" aria-labelledby=\"figcaption_attachment_227132\" class=\"wp-caption aligncenter\" ><img decoding=\"async\" class=\"wp-image-227132 size-full\" src=\"http:\/\/devblogs.microsoft.com\/java\/wp-content\/uploads\/sites\/51\/2019\/10\/TC-with-AOT.png\" alt=\"Figure 2: Tiered Compilation pipeline with AOT\" width=\"647\" height=\"600\" srcset=\"https:\/\/devblogs.microsoft.com\/java\/wp-content\/uploads\/sites\/51\/2019\/10\/TC-with-AOT.png 647w, https:\/\/devblogs.microsoft.com\/java\/wp-content\/uploads\/sites\/51\/2019\/10\/TC-with-AOT-300x278.png 300w\" sizes=\"(max-width: 647px) 100vw, 647px\" \/><figcaption id=\"figcaption_attachment_227132\" class=\"wp-caption-text\">Figure 2: Tiered Compilation pipeline with AOT<\/figcaption><\/figure><\/p>\n<p>&nbsp;<\/p>\n<p>The difference in generated code by the AOT compiler with and without support for TC is trivial. For the TC pipeline to decide whether to compile the method at a particular tier, the generated code updates a set of counters (invocation counters, backedge counters) when executing, and whenever any of these counters overflow a given threshold, the instrumented code calls back into the runtime. This call contains all the information needed by the runtime to figure out which method has reached the threshold. It allows the runtime to decide whether to compile the method at the next tier of the TC pipeline. Given that, if you generate code that does not have support for TC, then the counters are never updated, thus never overflowed, and it never calls back into the runtime to request compilation at the next tier of the TC pipeline.<\/p>\n<p>In case the code has been generated with support for TC, AOT code fits in the TC pipeline at roughly the same tier as the limited profile (2) tier. The threshold value differs, with, for example, the execution threshold to go from Tier 0 or Tier 2 to Tier 3: the default value without AOT is <span class=\"lang:default decode:true crayon-inline\">Tier3InvocationThreshold=200<\/span>, and the default value with AOT is <span class=\"lang:default decode:true crayon-inline\">Tier3AOTInvocationThreshold=10000<\/span>.<\/p>\n<h2>Usage<\/h2>\n<p>For the AOT compiler to successfully generate code, the same environment than for the JIT compiler need to be available. That means that all dependencies (jars, jmods) must be present and accessible to the AOT compiler.<\/p>\n<p><em>The example below is assuming you are using Java 11 or later.<\/em><\/p>\n<p>Let\u2019s take a simple example, HelloWorld.<\/p>\n<pre class=\"lang:default decode:true \">class HelloWorld {\r\n    public static void main(String args[]) {\r\n        System.out.println(\"Hello, World\");\r\n    }\r\n}<\/pre>\n<p>To compile it to Java bytecode, run the usual:<\/p>\n<pre class=\"lang:default decode:true \">$&gt; javac HelloWorld.java<\/pre>\n<p>If you want to run without AOT, you simply run:<\/p>\n<pre class=\"lang:default decode:true \">$&gt; java HelloWorld\r\nHello, World\r\n<\/pre>\n<p>If you want to run with AOT, you first need to run the AOT compiler:<\/p>\n<pre class=\"lang:default decode:true\">$&gt; jaotc --compile-for-tiered --output libHelloWorld.so --verbose HelloWorld\r\nCompiling libHelloWorld.so...\r\n1 classes found (25 ms)\r\nScanning HelloWorld\r\nadded &lt;init&gt;()V\r\nadded main([Ljava\/lang\/String;)V\r\n2 methods total, 2 methods to compile (4 ms)\r\nFreeing memory [used: 4.0 MB , comm: 12.0 MB, freeRatio ~= 66.7%] (44 ms)\r\nCompiling with 12 threads\r\n.\r\n2 methods compiled, 0 methods failed (363 ms)\r\nFreeing memory [used: 5.4 MB , comm: 18.0 MB, freeRatio ~= 70.0%] (17 ms)\r\nParsing compiled code (2 ms)\r\nFreeing memory [used: 5.8 MB , comm: 24.0 MB, freeRatio ~= 75.9%] (18 ms)\r\nProcessing metadata (10 ms)\r\nFreeing memory [used: 5.7 MB , comm: 24.0 MB, freeRatio ~= 76.2%] (18 ms)\r\nPreparing stubs binary (0 ms)\r\nPreparing compiled binary (0 ms)\r\n.header: 63 bytes\r\n.config: 43 bytes\r\n.kls.offsets: 336 bytes\r\n.meth.offsets: 52 bytes\r\n.kls.dependencies: 76 bytes\r\n.stubs.offsets: 1036 bytes\r\n.meth.metadata: 7832 bytes\r\n.text: 17800 bytes\r\n.code.segments: 137 bytes\r\n.meth.constdata: 14344 bytes\r\n.kls.got: 224 bytes\r\n.cnt.got: 48 bytes\r\n.meta.got: 32 bytes\r\n.meth.state: 360 bytes\r\n.oop.got: 8 bytes\r\n.meta.names: 2234 bytes\r\nFreeing memory [used: 5.7 MB , comm: 24.0 MB, freeRatio ~= 76.2%] (18 ms)\r\nCreating binary: libHelloWorld.o (14 ms)\r\nFreeing memory [used: 5.7 MB , comm: 24.0 MB, freeRatio ~= 76.2%] (18 ms)\r\nCreating shared library: libHelloWorld.so (19 ms)\r\nFinal memory\u00a0\u00a0 [used: 5.6 MB , comm: 24.0 MB, freeRatio ~= 76.8%]\r\nTotal time: 911 ms<\/pre>\n<p>Then to reference the code generated by the AOT compiler, run:<\/p>\n<pre class=\"lang:default decode:true \">$&gt; java -XX:AOTLibrary=.\/libHelloWorld.so HelloWorld\r\nHello, World<\/pre>\n<p>To verify if the AOT compiled code is loaded and executed, run the above command with <span class=\"lang:default decode:true crayon-inline \">-XX:+PrintAOT<\/span> and you should observe the following output:<\/p>\n<pre class=\"lang:default decode:true\">$&gt; java -XX:AOTLibrary=.\/libHelloWorld.so -XX:+PrintAOT HelloWorld\r\n17\u00a0\u00a0\u00a0 1\u00a0\u00a0\u00a0\u00a0 loaded\u00a0\u00a0\u00a0 .\/libHelloWorld.so\u00a0 aot library\r\n58\u00a0\u00a0\u00a0 1\u00a0\u00a0\u00a0\u00a0 aot[ 1]\u00a0\u00a0 HelloWorld.&lt;init&gt;()V\r\n58\u00a0\u00a0\u00a0 2\u00a0\u00a0\u00a0\u00a0 aot[ 1]\u00a0\u00a0 HelloWorld.main([Ljava\/lang\/String;)V\r\nHello, World<\/pre>\n<p>You can observe the output of <span class=\"lang:default decode:true crayon-inline\">-XX:PrintAOT<\/span> in the first three lines. Line 1 signals that <span class=\"lang:default decode:true crayon-inline \">.\/libHelloWorld.so<\/span> was correctly loaded. Lines 2 and 3 signal that the constructor <span class=\"lang:default decode:true crayon-inline \">HelloWorld.&lt;init&gt;()<\/span> and the main method <span class=\"lang:default decode:true crayon-inline \">HelloWorld.main()<\/span> were loaded and used for this execution of the application.<\/p>\n<h2>Alternatives<\/h2>\n<p>Other approaches to code generation apart from AOT are in development in Java. Similarly, to AOT, some of them focus on startup throughput (JIT at Startup and JIT Caching), while others focus on compilation footprint (JIT out of Process).<\/p>\n<h3>JIT at Startup<\/h3>\n<p>At startup, before the Java main() method executes, the JVM compiles a predefined set of methods. The C2 compiler is used to compile these methods and, because both the generation and the execution of the code happen in the same process, the C2 compiler doesn\u2019t require any modifications. PD is available alongside the predefined set of methods and is used to generate better code.<\/p>\n<p>The procedure to determine the predefined set of methods is simple: previous runs gather this information, both by saving the compiled methods and the PD used to compile these methods.<\/p>\n<p>This approach answers a very particular need: guaranteeing a high throughput from the get-go, at the expense of startup time.<\/p>\n<p>Two implementations are <a href=\"https:\/\/docs.azul.com\/zing\/Zing_AT_ReadyNow_ReadyNow.htm\">Azul ReadyNow<\/a>, available in Azul Zing, and <a href=\"https:\/\/openjdk.java.net\/jeps\/8203832\">JWarmup<\/a>, available in Alibaba Dragonwell (currently a draft JEP in OpenJDK).<\/p>\n<h3>JIT Caching<\/h3>\n<p>During the execution of an application, the JVM dumps the code generated to disk. It allows the JVM, at the next execution of the application, to only have to pick-up where it left off, loading the code previously generated from disk, and have a robust startup throughput. It differs from the AOT compiler in the requirement to run the application to generate the code, while the AOT compiler only involves parsing the application\u2019s code.<\/p>\n<p>This method requires persistent storage between runs of the application. It does require the same dependencies and environment between runs. Otherwise, you cannot always guarantee that the code generated on a previous run is compatible with the current one.<\/p>\n<p>Two implementations are <a href=\"https:\/\/www.eclipse.org\/openj9\/docs\/aot\/\">OpenJ9 Dynamic AOT<\/a> and <a href=\"https:\/\/docs.azul.com\/zing\/UseZVM_CompileStashing_Overview.htm\">Azul Compile Stashing<\/a>, available in Azul Zing.<\/p>\n<h3>Distributed JIT<\/h3>\n<p>This method assumes that offloading the code generation to another process on another machine has a smaller footprint than generating the code in-process. It works particularly well in constrained environments (for example, less than 512 MB of RAM and half of a CPU core) where you can off-load the code generation to bigger machines, freeing precious resources for the application. Moreover, it allows for system-level optimizations by allowing better caching of the generated code across many runs of the same application (ex: running Hadoop across dozens, hundreds of machines).<\/p>\n<p>The overall goal is not to reduce startup time or improve startup throughput \u2013 like AOT compilation, JIT at Startup, or JIT Caching \u2013 but to reduce the impact of the JIT compiler on the application footprint. That makes it a great complement to these other methods.<\/p>\n<p>An implementation is <a href=\"https:\/\/blog.openj9.org\/2019\/04\/01\/a-simple-jitaas-demo-on-docker-containers\/\">OpenJ9 JITaaS<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this blog post, we explored tradeoffs in the code generated by the AOT compiler (like Position Independent Code), and how the code fits in the TC pipeline. We also looked at other solutions in the Java ecosystem like JIT at Startup, JIT Caching and Distributed JIT, and how these solutions fit in the larger code generation aspect of the JVM.<\/p>\n<p>In a future post, we\u2019ll dig deeper into the implementation of <span style=\"font-size: 12pt;\"><span class=\"lang:default decode:true crayon-inline\">jaotc<\/span> <\/span>and how the code is loaded and used by HotSpot.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduce the AOT Compiler that was introduced in Java 9 with the addition of the jaotc command-line utility. Explore some of the tradeoffs it needs to take, how the generated code fits in the Tiered Compilation pipeline, go through a simple example, and take a look at some alternatives (JIT at Startup, JIT caching, and Distributed JIT).<\/p>\n","protected":false},"author":9372,"featured_media":227205,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[14,22,8],"tags":[237,30,248,7,26],"class_list":["post-227111","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cloud","category-desktop","category-open-source","tag-aot-compilation","tag-get-started","tag-java","tag-open-source","tag-performance"],"acf":[],"blog_post_summary":"<p>Introduce the AOT Compiler that was introduced in Java 9 with the addition of the jaotc command-line utility. Explore some of the tradeoffs it needs to take, how the generated code fits in the Tiered Compilation pipeline, go through a simple example, and take a look at some alternatives (JIT at Startup, JIT caching, and Distributed JIT).<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/posts\/227111","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/users\/9372"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/comments?post=227111"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/posts\/227111\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/media\/227205"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/media?parent=227111"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/categories?post=227111"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/java\/wp-json\/wp\/v2\/tags?post=227111"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}