Penyusunan Masa Depan (AoT)

1. Pengenalan

Dalam artikel ini, kita akan melihat Java Ahead of Time (AOT) Compiler, yang dijelaskan dalam JEP-295 dan ditambahkan sebagai ciri eksperimen di Java 9.

Pertama, kita akan melihat apa itu AOT, dan kedua, kita akan melihat contoh yang mudah. Ketiga, kita akan melihat beberapa sekatan AOT, dan terakhir, kita akan membincangkan beberapa kemungkinan kes penggunaan.

2. Apakah Penyusunan Masa Depan?

Penyusunan AOT adalah salah satu cara untuk meningkatkan prestasi program Java dan khususnya waktu permulaan JVM . JVM melaksanakan bytecode Java dan menyusun kod yang sering dilaksanakan ke kod asli. Ini dipanggil Penyusunan Just-in-Time (JIT). JVM memutuskan kod mana yang disusun JIT berdasarkan maklumat profil yang dikumpulkan semasa pelaksanaan.

Walaupun teknik ini membolehkan JVM menghasilkan kod yang sangat dioptimumkan dan meningkatkan prestasi puncak, waktu permulaan kemungkinan tidak optimum, kerana kod yang dilaksanakan belum disusun JIT. AOT bertujuan untuk memperbaiki tempoh pemanasan yang disebut ini . Penyusun yang digunakan untuk AOT adalah Graal.

Dalam artikel ini, kita tidak akan melihat JIT dan Graal secara terperinci. Sila rujuk artikel kami yang lain untuk gambaran keseluruhan peningkatan prestasi di Java 9 dan 10, serta menyelami Graal JIT Compiler.

3. Contoh

Untuk contoh ini, kami akan menggunakan kelas yang sangat sederhana, menyusunnya, dan melihat bagaimana menggunakan perpustakaan yang dihasilkan.

3.1. Penyusunan AOT

Mari lihat kelas sampel kami dengan cepat:

public class JaotCompilation { public static void main(String[] argv) { System.out.println(message()); } public static String message() { return "The JAOT compiler says 'Hello'"; } } 

Sebelum kita dapat menggunakan penyusun AOT, kita perlu menyusun kelas dengan penyusun Java:

javac JaotCompilation.java 

Kami kemudian meneruskan JaotCompilation.class yang dihasilkan ke penyusun AOT, yang terletak di direktori yang sama dengan penyusun Java standard:

jaotc --output jaotCompilation.so JaotCompilation.class 

Ini menghasilkan jaotCompilation.so perpustakaan dalam direktori semasa.

3.2. Menjalankan Program

Kami kemudian dapat melaksanakan program:

java -XX:AOTLibrary=./jaotCompilation.so JaotCompilation 

Hujah -XX: AOTLibrary menerima jalan relatif atau penuh ke perpustakaan. Sebagai alternatif, kita dapat menyalin perpustakaan ke folder lib di direktori beranda Java dan hanya meneruskan nama perpustakaan.

3.3. Mengesahkan bahawa Perpustakaan Dipanggil dan Digunakan

Kita dapat melihat bahawa perpustakaan memang dimuat dengan menambahkan -XX: + PrintAOT sebagai argumen JVM:

java -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so JaotCompilation 

Hasilnya akan kelihatan seperti:

77 1 loaded ./jaotCompilation.so aot library 

Namun, ini hanya memberitahu kita bahawa perpustakaan dimuat, tetapi tidak bahawa ia sebenarnya digunakan. Dengan menyampaikan argumen -verbose , kita dapat melihat bahawa kaedah di perpustakaan memang disebut:

java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation 

Output akan mengandungi baris:

11 1 loaded ./jaotCompilation.so aot library 116 1 aot[ 1] jaotc.JaotCompilation.()V 116 2 aot[ 1] jaotc.JaotCompilation.message()Ljava/lang/String; 116 3 aot[ 1] jaotc.JaotCompilation.main([Ljava/lang/String;)V The JAOT compiler says 'Hello' 

Perpustakaan yang disusun AOT mengandungi cap jari kelas , yang mesti sepadan dengan cap jari fail .class .

Mari ubah kod di JaotCompilation.java kelas untuk mengembalikan mesej yang berbeza:

public static String message() { return "The JAOT compiler says 'Good morning'"; } 

Sekiranya kita melaksanakan program tanpa AOT menyusun kelas yang diubah suai:

java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation 

Maka output hanya akan mengandungi:

 11 1 loaded ./jaotCompilation.so aot library The JAOT compiler says 'Good morning'

Kita dapat melihat bahawa kaedah di perpustakaan tidak akan dipanggil, kerana bytecode kelas telah berubah. Idea di sebalik ini adalah bahawa program akan selalu menghasilkan hasil yang sama, tidak kira sama ada perpustakaan yang disusun AOT dimuat atau tidak.

4. Lebih Banyak Hujah AOT dan JVM

4.1. AOT Penyusunan Modul Java

AOT juga dapat menyusun modul:

jaotc --output javaBase.so --module java.base 

Perpustakaan yang dihasilkan javaBase.so berukuran sekitar 320 MB dan memerlukan sedikit masa untuk dimuat. Ukurannya dapat dikurangkan dengan memilih pakej dan kelas yang akan disusun AOT.

Kami akan melihat bagaimana melakukannya di bawah, namun kami tidak akan menyelami semua butiran dengan mendalam.

4.2. Kompilasi Selektif dengan Perintah Kompilasi

To prevent the AOT compiled library of a Java module from becoming too large, we can add compile commands to limit the scope of what gets AOT compiled. These commands need to be in a text file – in our example, we'll use the file complileCommands.txt:

compileOnly java.lang.*

Then, we add it to the compile command:

jaotc --output javaBaseLang.so --module java.base --compile-commands compileCommands.txt 

The resulting library will only contain the AOT compiled classes in the package java.lang.

To gain real performance improvement, we need to find out which classes are invoked during the warm-up of the JVM.

This can be achieved by adding several JVM arguments:

java -XX:+UnlockDiagnosticVMOptions -XX:+LogTouchedMethods -XX:+PrintTouchedMethodsAtExit JaotCompilation 

In this article, we won't dive deeper into this technique.

4.3. AOT Compilation of a Single Class

We can compile a single class with the argument –class-name:

jaotc --output javaBaseString.so --class-name java.lang.String 

The resulting library will only contain the class String.

4.4. Compile for Tiered

By default, the AOT compiled code will always be used, and no JIT compilation will happen for the classes included in the library. If we want to include the profiling information in the library, we can add the argument compile-for-tiered:

jaotc --output jaotCompilation.so --compile-for-tiered JaotCompilation.class 

The pre-compiled code in the library will be used until the bytecode becomes eligible for JIT compilation.

5. Possible Use Cases for AOT Compilation

One use case for AOT is short running programs, which finish execution before any JIT compilation occurs.

Another use case is embedded environments, where JIT isn't possible.

At this point, we also need to note that the AOT compiled library can only be loaded from a Java class with identical bytecode, thus it cannot be loaded via JNI.

6. AOT and Amazon Lambda

A possible use case for AOT-compiled code is short-lived lambda functions where short startup time is important. In this section, we'll look at how we can run AOT compiled Java code on AWS Lambda.

Using AOT compilation with AWS Lambda requires the library to be built on an operating system that is compatible with the operating system used on AWS. At the time of writing, this is Amazon Linux 2.

Furthermore, the Java version needs to match. AWS provides the Amazon Corretto Java 11 JVM. In order to have an environment to compile our library, we'll install Amazon Linux 2 and Amazon Corretto in Docker.

We won't discuss all the details of using Docker and AWS Lambda but only outline the most important steps. For more information on how to use Docker, please refer to its official documentation here.

For more details about creating a Lambda function with Java, you can have a look at our article AWS Lambda With Java.

6.1. Configuration of Our Development Environment

First, we need to pull the Docker image for Amazon Linux 2 and install Amazon Corretto:

# download Amazon Linux docker pull amazonlinux # inside the Docker container, install Amazon Corretto yum install java-11-amazon-corretto # some additional libraries needed for jaotc yum install binutils.x86_64 

6.2. Compile the Class and Library

Inside our Docker container, we execute the following commands:

# create folder aot mkdir aot cd aot mkdir jaotc cd jaotc

The name of the folder is only an example and can, of course, be any other name.

package jaotc; public class JaotCompilation { public static int message(int input) { return input * 2; } }

The next step is to compile the class and library:

javac JaotCompilation.java cd .. jaotc -J-XX:+UseSerialGC --output jaotCompilation.so jaotc/JaotCompilation.class

Here, it's important to use the same garbage collector as is used on AWS. If our library cannot be loaded on AWS Lambda, we might want to check which garbage collector is actually used with the following command:

java -XX:+PrintCommandLineFlags -version

Now, we can create a zip file that contains our library and class file:

zip -r jaot.zip jaotCompilation.so jaotc/

6.3. Configure AWS Lambda

The last step is to log into the AWS Lamda console, upload the zip file and configure out Lambda with the following parameters:

  • Runtime: Java 11
  • Handler: jaotc.JaotCompilation::message

Furthermore, we need to create an environment variable with the name JAVA_TOOL_OPTIONS and set its value to:

-XX:+UnlockExperimentalVMOptions -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so

Pemboleh ubah ini membolehkan kita menyampaikan parameter ke JVM.

Langkah terakhir adalah mengkonfigurasi input untuk Lambda kami. Defaultnya adalah input JSON, yang tidak dapat diteruskan ke fungsi kita, oleh itu kita harus menetapkannya ke String yang berisi bilangan bulat, misalnya "1".

Akhirnya, kita dapat menjalankan fungsi Lambda dan harus melihat dalam log bahawa perpustakaan yang disusun AOT kami dimuat:

57 1 loaded ./jaotCompilation.so aot library

7. Kesimpulannya

Dalam artikel ini, kami melihat bagaimana AOT menyusun kelas dan modul Java. Oleh kerana ini masih merupakan ciri eksperimen, penyusun AOT bukan sebahagian daripada semua pengedaran. Contoh sebenar masih jarang ditemui, dan bergantung kepada masyarakat Java untuk mencari kes penggunaan terbaik untuk menggunakan AOT.

Semua coretan kod dalam artikel ini boleh didapati di repositori GitHub kami.