Скрещиваем ужа с ежом: OpenJDK-11 + GraalVM

в 14:13, , рубрики: graalvm, java, java 11, linux, maven

Привет! В свете не самых давних новостей про политику Oracle относительно лицензирования джавы всё острее встаёт вопрос ухода от оракловых версий в сторону OpenJDK. Оданко в OracleLabs уже давно делают весьма крутую штуку под названием GraalVM, который представляет из себя крутой JIT-компилятор, написанный на джаве, а также рантайм для запуска кода на таких языках как JavaScript, Ruby, Python, C, C++, Scala, Kotlin, R, Clojure. Впечатляет, правда? Но не о крутоте полиглот-среды я хочу вам рассказать. Речь пойдёт про сложности вкорячивания самой свежей сборки грааля в экосиситему OpenJDK 11 и чуток про производительность, совсем чуток…

Сначала было слово

История моего знакомства с graalvm началась на джокере в 2017 году. Там Chris Seaton очень подробно рассказал про внутренности компилятора и показал магию AOT компиляции на примере использования native-image из поставки грааля (это такая шутка, которая компилит твой джава код в нативный бинарник).

После того доклада я очень долго тренировался на компиляции нативного бинарника своего пет-проджекта, ставил костыли и грабли для того, чтобы заработал reflection во всех местах (будь он не ладен!) и, наконец, наткнулся на нерешенные проблемы с IO (что-то там не взлетело с zookeeper'ом, сейчас уже и не вспомню что). Плюнул пока на native-image :-(

Год 2018, всё тот же джокер и тот же graalvm в суперподробном докладе от Олега Шелаева про AOT.

В докладах и презентациях всё так здорово смотрится, а ещё на диске валяется пет-проджект… пора расчехлять терминал, качнуть свеженький релиз-кандидат грааля и в бой! Будем JIT трогать.

Hello World

Трогать и пинать свеженький JIT (на момент написания статьи — это версия ce-1.0.0-rc14) будем на примере куска кода для тестирования производительности с сайта https://graalvm.org — первый пример наш.

Какой же нынче джава-проект (даже Hello World) обходится без какой либо системы сборки? Правильно, только тот, на котором учатся готовить javac. Джавак готовить мы учиться не будем, пусть джаваком рулит maven.

Итак, встречайте, pom.xml:

pom.xml
    <?xml version="1.0"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.mycompany.app</groupId>
        <artifactId>my-app</artifactId>
        <!--<packaging>jar</packaging>-->
        <packaging>jar</packaging>
        <version>1.0-SNAPSHOT</version>
        <name>my-app</name>
        <url>http://maven.apache.org</url>
        <properties>
            <java.version>11</java.version>
            <graalvm.version>1.0.0-rc14</graalvm.version>
        </properties>
        <profiles>
            <profile>
                <id>jdk11</id>
                <activation>
                    <jdk>11</jdk>
                </activation>
                <build>
                    <plugins>
                        <plugin>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-dependency-plugin</artifactId>
                            <version>2.10</version>
                            <executions>
                                <execution>
                                    <id>copy</id>
                                    <phase>process-test-classes</phase>
                                    <goals>
                                        <goal>copy-dependencies</goal>
                                    </goals>
                                    <configuration>
                                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
                                    </configuration>
                                </execution>
                            </executions>
                        </plugin>
                        <plugin>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-jar-plugin</artifactId>
                            <configuration>
                                <archive>
                                    <manifest>
                                        <mainClass>com.mycompany.app.App</mainClass>
                                    </manifest>
                                </archive>
                            </configuration>
                        </plugin>
                    </plugins>
                </build>
            </profile>
        </profiles>
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <release>${java.version}</release>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.graalvm.compiler</groupId>
                <artifactId>compiler</artifactId>
                <version>${graalvm.version}</version>
            </dependency>
            <dependency>
                <groupId>org.graalvm.truffle</groupId>
                <artifactId>truffle-api</artifactId>
                <version>${graalvm.version}</version>
            </dependency>
            <dependency>
                <groupId>org.graalvm.sdk</groupId>
                <artifactId>graal-sdk</artifactId>
                <version>${graalvm.version}</version>
            </dependency>
            <dependency>
                <groupId>org.graalvm.js</groupId>
                <artifactId>js</artifactId>
                <version>${graalvm.version}</version>
            </dependency>
            <dependency>
                <groupId>org.graalvm.js</groupId>
                <artifactId>js-scriptengine</artifactId>
                <version>${graalvm.version}</version>
            </dependency>
        </dependencies>
    </project>
    

Структура файлов проекта выглядит так:

Скрещиваем ужа с ежом: OpenJDK-11 + GraalVM - 1

Код класса com.mycompany.app.App (копипаст примера с graalvm.org):

App.java

    package com.mycompany.app;

    public class App {

        static final int ITERATIONS = Math.max(Integer.getInteger("iterations", 1), 1);
        public static void main(String[] args) {
            String sentence = String.join(" ", args);
            for (int iter = 0; iter < ITERATIONS; iter++) {
                if (ITERATIONS != 1) System.out.println("-- iteration " + (iter + 1) + " --");
                long total = 0, start = System.currentTimeMillis(), last = start;
                for (int i = 1; i < 10_000_000; i++) {
                    total += sentence.chars().filter(Character::isUpperCase).count();
                    if (i % 1_000_000 == 0) {
                        long now = System.currentTimeMillis();
                        System.out.printf("%d (%d ms)%n", i / 1_000_000, now - last);
                        last = now;
                    }
                }
                System.out.printf("total: %d (%d ms)%n", total, System.currentTimeMillis() - start);
            }
        }

    }
    

Код module-info.java:

module-info.java
    module com.mycompany.app {}
    

Хм, пустой… А пустой он по той причине, что я хочу показать вам, как кастомная java (про кастомную джаву чуть позже) будет ругаться при необъявленных модулях, которые нужны нашему приложению.

Первый блин ...

не комом. Давайте соберём наш проект и запустим его. Собираем так:

mvn clean package

Запускаем:


$JAVA_HOME/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -Diterations=10 -jar target/my-app-1.0-SNAPSHOT.jar In 2017 I would like to run ALL languages in one VM.
    

Тут есть два важных момента: JVMCI — экспериментальная штука, появившаяся в джаве с 9-ой версии, поэтому нам нужно:

  • включить экспериментальные опции виртуальной машины —
    -XX:+UnlockEperimentalVMOptions
  • влючить ту самую экспериментальную виртуальную машину (начиная с девятки грааль есть в openjdk, не самой последней версии, но всё же) — -XX:+UseJVMCICompiler

Результат запуска

— iteration 1 — 1 (1466 ms)
2 (461 ms)
3 (463 ms)
4 (138 ms)
5 (151 ms)
6 (159 ms)
7 (266 ms)
8 (128 ms)
9 (144 ms)
total: 69999993 (3481 ms)
— iteration 2 — 1 (233 ms)
2 (169 ms)
3 (121 ms)
4 (205 ms)
5 (170 ms)
6 (152 ms)
7 (227 ms)
8 (158 ms)
9 (108 ms)
total: 69999993 (1644 ms)
— iteration 3 — 1 (98 ms)
2 (102 ms)
3 (98 ms)
4 (102 ms)
5 (95 ms)
6 (96 ms)
7 (101 ms)
8 (95 ms)
9 (97 ms)
total: 69999993 (990 ms)
— iteration 4 — 1 (109 ms)
2 (114 ms)
3 (97 ms)
4 (98 ms)
5 (100 ms)
6 (103 ms)
7 (125 ms)
8 (108 ms)
9 (100 ms)
total: 69999993 (1056 ms)
— iteration 5 — 1 (98 ms)
2 (100 ms)
3 (105 ms)
4 (97 ms)
5 (95 ms)
6 (99 ms)
7 (95 ms)
8 (123 ms)
9 (98 ms)
total: 69999993 (1010 ms)
— iteration 6 — 1 (99 ms)
2 (95 ms)
3 (102 ms)
4 (99 ms)
5 (96 ms)
6 (100 ms)
7 (99 ms)
8 (99 ms)
9 (104 ms)
total: 69999993 (993 ms)
— iteration 7 — 1 (100 ms)
2 (104 ms)
3 (95 ms)
4 (96 ms)
5 (97 ms)
6 (95 ms)
7 (94 ms)
8 (108 ms)
9 (108 ms)
total: 69999993 (1000 ms)
— iteration 8 — 1 (100 ms)
2 (106 ms)
3 (99 ms)
4 (95 ms)
5 (97 ms)
6 (97 ms)
7 (101 ms)
8 (99 ms)
9 (101 ms)
total: 69999993 (1012 ms)
— iteration 9 — 1 (105 ms)
2 (97 ms)
3 (98 ms)
4 (96 ms)
5 (99 ms)
6 (96 ms)
7 (94 ms)
8 (98 ms)
9 (105 ms)
total: 69999993 (993 ms)
— iteration 10 — 1 (107 ms)
2 (98 ms)
3 (99 ms)
4 (100 ms)
5 (97 ms)
6 (101 ms)
7 (98 ms)
8 (103 ms)
9 (105 ms)
total: 69999993 (1006 ms)

Что мы тут видим? А видим то, что первая итерация самая долгая (3,5 секунды), это JIT разогревается. А дальше всё более или менее ровно (в пределах одной секунды).

А что если дадим джаве свежую версию грааля? Сказано — сделано:


    java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -Diterations=10 --module-path=target/lib --upgrade-module-path=target/lib/compiler-1.0.0-rc14.jar -jar target/my-app-1.0-SNAPSHOT.jar In 2017 I would like to run ALL languages in one VM.
    

Результат запуска со свежим граалем

— iteration 1 — 1 (1789 ms)
2 (547 ms)
3 (313 ms)
4 (87 ms)
5 (88 ms)
6 (87 ms)
7 (83 ms)
8 (92 ms)
9 (87 ms)
total: 69999993 (3259 ms)
— iteration 2 — 1 (241 ms)
2 (161 ms)
3 (152 ms)
4 (195 ms)
5 (136 ms)
6 (129 ms)
7 (154 ms)
8 (176 ms)
9 (109 ms)
total: 69999993 (1553 ms)
— iteration 3 — 1 (109 ms)
2 (103 ms)
3 (113 ms)
4 (172 ms)
5 (141 ms)
6 (148 ms)
7 (111 ms)
8 (102 ms)
9 (101 ms)
total: 69999993 (1211 ms)
— iteration 4 — 1 (96 ms)
2 (96 ms)
3 (104 ms)
4 (98 ms)
5 (96 ms)
6 (97 ms)
7 (98 ms)
8 (96 ms)
9 (95 ms)
total: 69999993 (972 ms)
— iteration 5 — 1 (97 ms)
2 (93 ms)
3 (99 ms)
4 (97 ms)
5 (97 ms)
6 (97 ms)
7 (95 ms)
8 (98 ms)
9 (94 ms)
total: 69999993 (965 ms)
— iteration 6 — 1 (96 ms)
2 (95 ms)
3 (96 ms)
4 (99 ms)
5 (102 ms)
6 (94 ms)
7 (99 ms)
8 (115 ms)
9 (109 ms)
total: 69999993 (1001 ms)
— iteration 7 — 1 (98 ms)
2 (96 ms)
3 (99 ms)
4 (98 ms)
5 (118 ms)
6 (98 ms)
7 (95 ms)
8 (99 ms)
9 (116 ms)
total: 69999993 (1017 ms)
— iteration 8 — 1 (95 ms)
2 (99 ms)
3 (99 ms)
4 (106 ms)
5 (101 ms)
6 (101 ms)
7 (93 ms)
8 (97 ms)
9 (108 ms)
total: 69999993 (993 ms)
— iteration 9 — 1 (102 ms)
2 (95 ms)
3 (97 ms)
4 (125 ms)
5 (94 ms)
6 (101 ms)
7 (100 ms)
8 (95 ms)
9 (96 ms)
total: 69999993 (1008 ms)
— iteration 10 — 1 (97 ms)
2 (97 ms)
3 (99 ms)
4 (112 ms)
5 (102 ms)
6 (96 ms)
7 (96 ms)
8 (98 ms)
9 (96 ms)
total: 69999993 (988 ms)

Результат, как мы видим, несильно отличается.

Забыл. Мы ж не попробовали запустить то же самое без новмодного JIT-компилятора. Сделаем:


    java -Diterations=10 -jar target/my-app-1.0-SNAPSHOT.jar In 2017 I would like to run ALL languages in one VM.
    

Резульутат без новомодного JIT-компилятора

— iteration 1 — 1 (372 ms)
2 (271 ms)
3 (337 ms)
4 (391 ms)
5 (328 ms)
6 (273 ms)
7 (239 ms)
8 (271 ms)
9 (250 ms)
total: 69999993 (2978 ms)
— iteration 2 — 1 (242 ms)
2 (253 ms)
3 (253 ms)
4 (240 ms)
5 (245 ms)
6 (275 ms)
7 (273 ms)
8 (263 ms)
9 (234 ms)
total: 69999993 (2533 ms)
— iteration 3 — 1 (237 ms)
2 (235 ms)
3 (234 ms)
4 (246 ms)
5 (242 ms)
6 (238 ms)
7 (244 ms)
8 (243 ms)
9 (253 ms)
total: 69999993 (2414 ms)
— iteration 4 — 1 (244 ms)
2 (249 ms)
3 (245 ms)
4 (243 ms)
5 (232 ms)
6 (256 ms)
7 (321 ms)
8 (303 ms)
9 (249 ms)
total: 69999993 (2599 ms)
— iteration 5 — 1 (246 ms)
2 (242 ms)
3 (248 ms)
4 (256 ms)
5 (280 ms)
6 (233 ms)
7 (235 ms)
8 (266 ms)
9 (246 ms)
total: 69999993 (2511 ms)
— iteration 6 — 1 (292 ms)
2 (368 ms)
3 (339 ms)
4 (251 ms)
5 (267 ms)
6 (259 ms)
7 (289 ms)
8 (262 ms)
9 (357 ms)
total: 69999993 (3058 ms)
— iteration 7 — 1 (284 ms)
2 (258 ms)
3 (248 ms)
4 (247 ms)
5 (266 ms)
6 (247 ms)
7 (242 ms)
8 (314 ms)
9 (265 ms)
total: 69999993 (2656 ms)
— iteration 8 — 1 (239 ms)
2 (238 ms)
3 (257 ms)
4 (282 ms)
5 (244 ms)
6 (261 ms)
7 (253 ms)
8 (295 ms)
9 (256 ms)
total: 69999993 (2575 ms)
— iteration 9 — 1 (273 ms)
2 (243 ms)
3 (239 ms)
4 (240 ms)
5 (250 ms)
6 (285 ms)
7 (266 ms)
8 (285 ms)
9 (264 ms)
total: 69999993 (2617 ms)
— iteration 10 — 1 (245 ms)
2 (264 ms)
3 (258 ms)
4 (253 ms)
5 (239 ms)
6 (260 ms)
7 (251 ms)
8 (250 ms)
9 (256 ms)
total: 69999993 (2538 ms)

Результат отличается, и прилично.

С2 не даёт никаких оптимизаций на горячих кусках кода — каждая итерация с одним и тем же временем.

Graal же умеет оптимизировать горячие куски кода и в перспективе даёт хороший прирост производительности.

Чтобы что?

Это, пожалуй, главный вопрос, который необходимо задавать себе и другим (участникам команды), когда в проект хотим добавить новую фичу, новый тул, новую вирутальную машину, новый JIT…

Моя история, как писалось выше, началась с Joker 2017, потом были долгие попытки осилить AOT, а теперь вот вкушаю прелести JIT'а для джавы на джаве.

Пет-проджект на диске представляет из себя некий движок бизнес-процессов, где процессы рисуются прикладными разработчиками в UI в браузере, и у них есть возможность писать скрипты на JS, которые будут бежать на JVM.

В будущих версиях джавы nashorn обещают убрать, GraalVM постепенно близится к релизу…

Что ж, ответ на вопрос такой:

  1. хотим рантайм для запуска JS (и не только)
  2. хотим скоростной JIT
  3. хотим, чтобы запуск приложений пет-проджекта с виду был как прежде на 8-ке (без всяких
    --module-path

    и

    --upgrade-module-path

    , но со свежей сборкой грааля)

Jlink

Первые два пункта в списке ответов на поставленный выше вопрос достаточно понятны, давайте разбираться с последним.

Дело в том, что разработчики-админы-девопсы люди ленивые и делать лишнюю работу не любят (я тоже такой), пытаются всё автоматизировать и запаковать в готовый бандл, который можно запустить как простой бинарь. Что ж, задача есть, давайте её решать.

На помощь к нам приходит относительно новый тул из мира Java 9+, и имя ему jlink. Пробуем запаковать наше приложение со всеми необходимыми либами в бандл:


jlink --module-path target/classes:target/lib:$JAVA_HOME/jmods --add-modules com.mycompany.app --launcher app=com.mycompany.app/com.mycompany.app.App --compress 2 --no-header-files --no-man-pages --strip-debug --output test
    

Как много параметров всяких, опишем основные:

  • --module-path target/classes:target/lib:$JAVA_HOME/jmods
  • --add-modules com.mycompany.app
  • --launcher app=com.mycompany.app/com.mycompany.app.App
  • --output test

Про остальные параметры можно спросить у дядюшки гугла, все они направлены на то, чтобы уменьшить итоговый размер бандла.

Посмотрим на результат:

Скрещиваем ужа с ежом: OpenJDK-11 + GraalVM - 2

Внутри test/bin/app лежит простой sh-скрипт, который запускает наше приложение на той джаве, что лежит рядом с app:


        #!/bin/sh
JLINK_VM_OPTIONS="-Diterations=10" #системный параметр мы уже ручками задефайнили, изначально переменная тут с пустым значением
DIR=`dirname $0`
$DIR/java $JLINK_VM_OPTIONS -m com.mycompany.app/com.mycompany.app.App $@
    

Запустим test/bin/app на C2:


    ./test/bin/app In 2017 I would like to run ALL languages in one VM.
    

Результат

— iteration 1 — 1 (315 ms)
2 (231 ms)
3 (214 ms)
4 (297 ms)
5 (257 ms)
6 (211 ms)
7 (217 ms)
8 (245 ms)
9 (222 ms)
total: 69999993 (2424 ms)
— iteration 2 — 1 (215 ms)
2 (215 ms)
3 (223 ms)
4 (224 ms)
5 (217 ms)
6 (208 ms)
7 (208 ms)
8 (222 ms)
9 (222 ms)
total: 69999993 (2164 ms)
— iteration 3 — 1 (206 ms)
2 (226 ms)
3 (234 ms)
4 (211 ms)
5 (212 ms)
6 (213 ms)
7 (210 ms)
8 (245 ms)
9 (223 ms)
total: 69999993 (2216 ms)
— iteration 4 — 1 (222 ms)
2 (233 ms)
3 (220 ms)
4 (222 ms)
5 (221 ms)
6 (219 ms)
7 (222 ms)
8 (216 ms)
9 (220 ms)
total: 69999993 (2215 ms)
— iteration 5 — 1 (231 ms)
2 (230 ms)
3 (221 ms)
4 (226 ms)
5 (227 ms)
6 (223 ms)
7 (215 ms)
8 (216 ms)
9 (219 ms)
total: 69999993 (2234 ms)
— iteration 6 — 1 (227 ms)
2 (218 ms)
3 (221 ms)
4 (254 ms)
5 (222 ms)
6 (212 ms)
7 (214 ms)
8 (222 ms)
9 (222 ms)
total: 69999993 (2241 ms)
— iteration 7 — 1 (217 ms)
2 (225 ms)
3 (222 ms)
4 (223 ms)
5 (227 ms)
6 (221 ms)
7 (219 ms)
8 (226 ms)
9 (219 ms)
total: 69999993 (2217 ms)
— iteration 8 — 1 (218 ms)
2 (242 ms)
3 (219 ms)
4 (218 ms)
5 (224 ms)
6 (226 ms)
7 (223 ms)
8 (220 ms)
9 (219 ms)
total: 69999993 (2228 ms)
— iteration 9 — 1 (234 ms)
2 (218 ms)
3 (217 ms)
4 (217 ms)
5 (225 ms)
6 (222 ms)
7 (216 ms)
8 (226 ms)
9 (214 ms)
total: 69999993 (2212 ms)
— iteration 10 — 1 (226 ms)
2 (230 ms)
3 (215 ms)
4 (238 ms)
5 (225 ms)
6 (218 ms)
7 (218 ms)
8 (215 ms)
9 (228 ms)
total: 69999993 (2233 ms)

Теперь на graalvm (определив необходимые для запуска флаги в переменной JLINK_VM_OPTIONS):

test/bin/app


 #!/bin/sh
JLINK_VM_OPTIONS="-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -Diterations=10"
DIR=`dirname $0`
$DIR/java $JLINK_VM_OPTIONS -m com.mycompany.app/com.mycompany.app.App $@
        

Результат:


Error occurred during initialization of boot layer
java.lang.module.FindException: Module jdk.internal.vm.ci not found
    

Ну вот, приплыли… А теперь вспомним, что мы работаем с java 11 в модульном окружении, собираем приложение как модуль, а про используемые модули ничего никому не сказали. Пора исправляться.

Новая версия module-info.java:

module com.mycompany.app {
    requires jdk.internal.vm.compiler;
    requires org.graalvm.sdk;
    requires org.graalvm.truffle;
    requires transitive org.graalvm.js;
    requires transitive org.graalvm.js.scriptengine;
}
    

Собираем, удаляем директорию test, линкуем.

Результат:


Error: automatic module cannot be used with jlink: icu4j from file:///home/slava/JavaProjects/graal-js-jdk11-maven-demo/target/lib/icu4j-62.1.jar
    

Что за «автоматик модуль кеннот би юзд»? А это jlink нам говорит, что либа icu4j не cодержит в себе module-info.class. Что нужно, чтобы такой класс появился внутри указанной либы:

  • понять список используемых либой модулей и создать module-info.java, определить все package'и, которые должны быть видны снаружи
  • скомпилировать module-info.java для либы
  • засунуть скомпилированный module-info.java в джарник с либой

Поехали!

Файл module-info.java со всем содержимым сгенерирует для наc утилита jdeps из состава openjdk-11:

Скрещиваем ужа с ежом: OpenJDK-11 + GraalVM - 3

Компилируем module-info.java для либы icu4j:

Скрещиваем ужа с ежом: OpenJDK-11 + GraalVM - 4

Обновляем джарник, заталкивая в него module-info.class:


        $JAVA_HOME/bin/jar uf target/lib/icu4j-62.1.jar -C target/modules module-info.class
    

Линкуем, запускаем.

Результат

— iteration 1 — 1 (1216 ms)
2 (223 ms)
3 (394 ms)
4 (138 ms)
5 (116 ms)
6 (102 ms)
7 (120 ms)
8 (106 ms)
9 (110 ms)
total: 69999993 (2619 ms)
— iteration 2 — 1 (166 ms)
2 (133 ms)
3 (142 ms)
4 (157 ms)
5 (119 ms)
6 (134 ms)
7 (153 ms)
8 (95 ms)
9 (85 ms)
total: 69999993 (1269 ms)
— iteration 3 — 1 (86 ms)
2 (81 ms)
3 (87 ms)
4 (83 ms)
5 (85 ms)
6 (100 ms)
7 (87 ms)
8 (83 ms)
9 (85 ms)
total: 69999993 (887 ms)
— iteration 4 — 1 (84 ms)
2 (86 ms)
3 (88 ms)
4 (91 ms)
5 (85 ms)
6 (88 ms)
7 (87 ms)
8 (85 ms)
9 (85 ms)
total: 69999993 (864 ms)
— iteration 5 — 1 (94 ms)
2 (86 ms)
3 (84 ms)
4 (83 ms)
5 (85 ms)
6 (86 ms)
7 (84 ms)
8 (84 ms)
9 (83 ms)
total: 69999993 (854 ms)
— iteration 6 — 1 (83 ms)
2 (89 ms)
3 (87 ms)
4 (87 ms)
5 (86 ms)
6 (86 ms)
7 (91 ms)
8 (86 ms)
9 (85 ms)
total: 69999993 (865 ms)
— iteration 7 — 1 (87 ms)
2 (86 ms)
3 (88 ms)
4 (90 ms)
5 (91 ms)
6 (87 ms)
7 (85 ms)
8 (85 ms)
9 (86 ms)
total: 69999993 (868 ms)
— iteration 8 — 1 (84 ms)
2 (85 ms)
3 (86 ms)
4 (84 ms)
5 (84 ms)
6 (88 ms)
7 (85 ms)
8 (86 ms)
9 (86 ms)
total: 69999993 (852 ms)
— iteration 9 — 1 (83 ms)
2 (85 ms)
3 (84 ms)
4 (85 ms)
5 (89 ms)
6 (85 ms)
7 (88 ms)
8 (86 ms)
9 (83 ms)
total: 69999993 (850 ms)
— iteration 10 — 1 (83 ms)
2 (84 ms)
3 (83 ms)
4 (82 ms)
5 (85 ms)
6 (83 ms)
7 (84 ms)
8 (94 ms)
9 (93 ms)
total: 69999993 (856 ms)

УРА! У нас получилось! Теперь мы имеем забандленное приложение в виде запускаемого sh-скрипта со своей джавой, со всеми необходимыми модулями (включая свежий graalvm), с преферансом и барышнями.

P.S.

Java не даёт скучать и даёт новую пищу для ума с каждым релизом. Пробуйте новые фичи, экспериментируйте, делитесь опытом. Надеюсь, скоро напишу статью про то как забандлил часть пет-проджекта с граалем (там vert.x, асинхронщина и js-срипты — будет интересно).

И ещё… это моя первая статья на Хабре, — прошу, не сильно бейте.

Автор: zhulikovatyi

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js