Хабрахабр

[Из песочницы] Скрещиваем ужа с ежом: OpenJDK-11 + GraalVM

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

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

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

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

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

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

Hello World

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

Правильно, только тот, на котором учатся готовить javac. Какой же нынче джава-проект (даже Hello World) обходится без какой либо системы сборки? Джавак готовить мы учиться не будем, пусть джаваком рулит 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>$/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>

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

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

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-ой версии, поэтому нам нужно:

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

— 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

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

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

Внутри 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:

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

Обновляем джарник, заталкивая в него 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-срипты — будет интересно).

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»