Не так давно Oracle выпустил первый релиз проекта GraalVM (https://www.graalvm.org/). Релизу был сразу присвоен номер 19.0.0, видимо, для того чтобы убедить, что проект зрелый и готов к использованию в серьезных приложениях. Одна из частей этого проекта: Substrate VM — фреймворк, который позволяет превращать Java-приложения в нативные исполняемые файлы (а также нативные библиотеки, которые можно подключать в приложениях, написанных, например, на С/С++). Эта возможность пока объявлена экспериментальной. Также стоит отметить, что у нативных приложений на Java есть некоторые ограничения: необходимо перечислять все используемые ресурсы, чтобы включить их в нативную программу; нужно перечислить все классы, которые будут использоваться с помощью reflection и другие ограничения. Полный список указан тут Native Image Java Limitations. Изучив этот список, в принципе понятно, что ограничения не такие значительные, чтобы нельзя было разработать более сложные приложения, чем хелловорлды. Мною была поставлена такая цель: разработка небольшой программы, которая имеет встроенный web-сервер, использует базу данных (через ORM библиотеку) и компилируется в нативный бинарник, который может запускаться на системах без установленной Java машины.

Экспериментировать я буду на Ubuntu 19.04 (Intel Core i3-6100 CPU @ 3.70GHz ? 4).

Установка GraalVM


Установку GraalVM удобно производить с помощью SDKMAN. Команда установки GraalVM:

sdk install java 19.0.0-grl

Выполнится установка OpenJDK GraalVM CE 19.0.0, CE — это Community Edition. Есть еще Enterprise Edition (EE), но эту редакцию нужно скачивать с Oracle Technology Network, ссылка находятся на странице GraalVM Downloads.

После установки GraalVM, уже с помощью менеджера обновления компонентов gu из GraalVM, я установил поддержку компиляции в нативный бинарник —

gu install native-image

Все, рабочие инструменты готовы, теперь можно начать разрабатывать приложения.

Простое нативное приложение


В качестве системы сборки я использую Maven. Для создания нативных бинарников есть maven plugin:

native-image-maven-plugin
<build>
    <plugins>
        <plugin>
            <groupId>com.oracle.substratevm</groupId>
            <artifactId>native-image-maven-plugin</artifactId>
            <version>${graal.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>native-image</goal>
                    </goals>
                    <phase>package</phase>
                </execution>
            </executions>
            <configuration>
                <imageName>nativej</imageName>
                <buildArgs>
                    --no-server
                </buildArgs>
            </configuration>
        </plugin>
    </plugins>
</build>


Еще требуется задать main класс приложения. Это можно сделать как в native-image-maven-plugin, так и традиционным способом, через:

maven-jar-plugin
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <archive>
            <manifest>
                <mainClass>nativej.Startup</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>


Создадим main класс:

Startup.java
public class Startup {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}


Теперь можно выполнить команду maven для сборки приложения:

mvn clean package

Сборка нативного бинарника на моей машине занимает 35 секунд. В результате в каталоге target получается бинарный файл размером 2.5 MB. Программа не требует установленной Java машины и запускается на машинах, где отсутствует Java.

Ссылка на репозиторий: Github: native-java-helloworld-demo.

JDBC драйвер Postgres


И так, простое приложение работает, выводит «Hello world». Решения каких-то проблем не потребовалось. Попробую перейти на уровень выше: подключу драйвер JDBC Postgres для запроса данных из базы данных. В Issues на гитхабе GraalVM попадаются баги, связанные с драйвером Postgres, но на релиз-кандидаты GraalVM. Все они отмечены как исправленные.

Подключаю зависимость postgresql:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>

Пишу код извлечения данных из базы данных (была создана простейшая табличка users):

Startup.java
public class Startup {
    public static void main(String[] args) SQLException {
        final PGSimpleDataSource ds = new PGSimpleDataSource();
        ds.setUrl("jdbc:postgresql://localhost/demo_nativem");
        ds.setUser("test");
        ds.setPassword("test");

        try (
                Connection conn = ds.getConnection();
                Statement stmt = conn.createStatement();
                ResultSet rs = stmt.executeQuery("SELECT * FROM \"public\".\"user\"");
        ) {
            while(rs.next()){
                System.out.print("ID: " + rs.getLong("id"));
                System.out.println(", Name: " + rs.getString("name"));
            }
        }
    }
}


Собираю нативный бинарник и сразу получаю ошибку сборки:

Error: No instances are allowed in the image heap for a class that is initialized or reinitialized at image runtime: org.postgresql.Driver. Try marking this class for build-time initialization with --initialize-at-build-time=org.postgresql.Driver


Дело в том, что сборщик нативного приложения инициализирует все static поля в процессе сборки (если не указано иначе), причем он делает это, исследуя зависимости классов. Мой код не ссылается на org.postgresql.Driver, поэтому сборщик не знает как его лучше инициализировать (при сборке, либо при старте приложения) и предлагает зарегистрировать его для инициализации при сборке. Это можно сделать, добавив его в аргументы maven плагина native-image-maven-plugin, как указано в описании ошибки. После добавления Driver получаю еще такую же ошибку, связанную с org.postgresql.util.SharedTimer. Снова собираю и сталкиваюсь с такой ошибкой сборки:

Error: Class initialization failed: org.postgresql.sspi.SSPIClient


Тут уже нет рекомендаций по исправлению. Но, посмотрев на исходник класса, понятно, что он относится к выполнению кода под Windows. На Linux его инициализация (которая происходит при сборке) падает с ошибкой. Есть возможность отложить его инициализацию на старт приложения: --initialize-at-run-time=org.postgresql.sspi.SSPIClient. Инициализация на Linux не будет происходить и ошибок, связанных с этим классом мы больше не получим. Аргументы сборки:

<buildArgs>
    --no-server
    --no-fallback
    --initialize-at-build-time=org.postgresql.Driver
    --initialize-at-build-time=org.postgresql.util.SharedTimer
    --initialize-at-run-time=org.postgresql.sspi.SSPIClient
</buildArgs>

Сборка стала занимать уже 1 минуту 20 секунд и файл распух до 11 MB. Я добавил дополнительный флаг для сборки бинарника: --no-fallback запрещает генерить нативный бинарник, который требует установленной Java машины. Такой бинарник создается, если сборщик обнаруживает использование фич языка, которые либо не поддерживаются в Substrate VM, либо требуют настройки, но настройка пока отсутствует. В моем случае сборщик обнаружил потенциальное использование рефлекшена в драйвере JDBC. Но это только потенциальное использование, в моей программе оно не требуется и поэтому и не требуется дополнительная настройка (как ее делать будет показано дальше). Существует еще флаг --static, который заставляет генератор статически линковать libc. Но если его использовать, то программа падает с segmentation fault при попытке разрешить сетевое имя в IP адрес. Я поискал какие-либо решения этой проблемы, но не нашел ничего подходящего, поэтому оставил зависимость программы на libc.

Запускаю получившийся бинарник и получаю следующую ошибку:

Exception in thread "main" org.postgresql.util.PSQLException: Could not find a java cryptographic algorithm: TLS SSLContext not available.


После некоторых исследований была выявлена причина ошибки: Postgres по умолчанию устанавливает TLS соединение с использованием Elliptic Curve. В SubstrateVM не входит реализация таких алгоритмов для TLS, вот соответствующий открытый issue — Single-binary ECC (ECDSA/ECDHE) TLS support for SubstrateVM. Вариантов решения несколько: положить рядом с приложением библиотеку из поставки GraalVM: libsunec.so, на сервере Postgres настроить список алгоритмов, исключив Elliptic Curve алгоритмы или просто отключить установку TLS соединения в драйвере Postgres (этот вариант и был выбран):

dataSource.setSslMode(SslMode.DISABLE.value);

Устранив ошибку создания соединения с Postgres, запускаю нативное приложение, оно выполняется и выводит данные из базы данных.

Ссылка на репозиторий: Github: native-java-postgres-demo.

DI framework и встроенный web-сервер


При разработке сложного приложения на Java обычно используют какой-нибудь framework, например, Spring Boot. Но судя по этой статье GraalVM native image support, работу Spring Boot в native image «из коробки» нам обещают только в версии Spring Boot 5.3.

Но есть замечательный framework Micronaut, в котором заявлена работа в GraalVM native image. В целом подключение Micronaut к приложению, которое будет собираться в бинарник, не требует каких-то специальных настроек и решения проблем. Действительно, многие настройки использования рефлекшена и подключения ресурсов для Substrate VM уже сделаны внутри Micronaut. Кстати, такие же настройки можно разместить и внутри своего приложения в файле настроек META-INF/native-image/${groupId}/${artifactId}/native-image.properties (такой путь для файла настроек рекомендует Substrate VM), вот типичное содержание файла:

native-image.properties
Args =   -H:+ReportUnsupportedElementsAtRuntime   -H:ResourceConfigurationResources=${.}/resource-config.json   -H:ReflectionConfigurationResources=${.}/reflect-config.json   -H:DynamicProxyConfigurationResources=${.}/proxy-config.json   --initialize-at-build-time=org.postgresql.Driver   --initialize-at-build-time=org.postgresql.util.SharedTimer   --initialize-at-run-time=org.postgresql.sspi.SSPIClient


Файлы resource-config.json, reflect-config.json, proxy-config.json содержат настройки подключения ресурсов, рефлекшена и использованных прокси (Proxy.newProxyInstance). Эти файлы можно создать вручную или получить, используя agentlib:native-image-agent. В случае использования native-image-agent нужно запустить обычный jar (а не нативный бинарник) с использованием агента:


java -agentlib:native-image-agent=config-output-dir=output -jar my.jar

где output — каталог, в котором разместятся файлы, описанные выше. При этом программу нужно не просто запустить, но и исполнить сценарии в программе, потому что в файлы записываются настройки по мере использования рефлекшена, открытия ресурсов, создания прокси. Эти файлы можно поместить META-INF/native-image/${groupId}/${artifactId} и сослаться на них в native-image.properties.

Я решил подключить логирование с помощью logback: добавил зависимость на библиотеку logback-classic и файл logback.xml. После этого собрал обычный jar и запустил его с использованием native-image-agent. При завершении программы нужные файлы настроек. Если посмотреть их содержимое, можно увидеть, что агент зарегистрировал использование logback.xml, чтобы вкомпилить в бинарник. Также, в файл reflect-config.json попали все случаи использования рефлекшена: для заданных классов в бинарник попадет мета-информация.

Затем я добавил зависимость на библиотеку micronaut-http-server-netty для использования встроенного web-сервера на основе netty и создал контроллер:

Startup.java
@Controller("/hello")
public class HelloController {
    @Get("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public HttpResponse<String> hello(String name) {
        return HttpResponse.ok("Hello " + name);
    }
}


И main class:

HelloController.java
public class Startup {
    public static void main(String[] args) {
        Signal.handle(new Signal("INT"), sig -> System.exit(0));

        Micronaut.run(Startup.class, args);
    }
}


Теперь можно попробовать собрать нативный бинарник. У меня сборка заняла 4 минуты. Если его запустить и перейти по адресу http://localhost:8080/hello/user то выпадает ошибка:

{"_links":{"self":{"href":"/hello/user","templated":false}},"message":"More than 1 route matched the incoming request. The following routes matched /hello/user: GET - /hello/user, GET - /hello/user"}

Честно говоря не совсем ясно, почему так происходит, но после исследования методом тыка я обнаружил, что ошибка исчезает, если из файла resource-config.json (который был создан агентом) убрать следующие строки:

    {"pattern":"META-INF/services/com.fasterxml.jackson.databind.Module"}, 
    {"pattern":"META-INF/services/io.micronaut.context.env.PropertySourceLoader"}, 
    {"pattern":"META-INF/services/io.micronaut.http.HttpResponseFactory"}, 
    {"pattern":"META-INF/services/io.micronaut.inject.BeanConfiguration"}, 
    {"pattern":"META-INF/services/io.micronaut.inject.BeanDefinitionReference"}, 

Эти ресурсы регистрирует Micronaut и похоже, что повторная регистрация приводит к двойной регистрации моего контроллера и ошибке. Если после исправления файла пересобрать бинарник и запустить, то ошибок уже не будет, по адресу http://localhost:8080/hello/user выведется текст «Hello user».

Хочу обратить внимание на использование в main классе следующей строчки:

Signal.handle(new Signal("INT"), sig -> System.exit(0));

Ее нужно вставлять для корректного завершения Micronaut. Несмотря на то, что Micronaut вешает хук на завершение работы, он не срабатывает в нативном бинарнике. Есть соответствующий issue: Shutdownhook not firing with native. Он отмечен как исправленный, но, по факту, в нем только обходное решение с использованием класса Signal.

Ссылка на репозиторий: Github: native-java-postgres-micronaut-demo.

Подключение ORM


JDBC это хорошо, но утомляет повторяющимся кодом, бесконечными SELECT и UPDATE. Попробую облегчить (или усложнить, смотря с какой стороны смотреть) себе жизнь, подключив какой-нибудь ORM.

Hibernate


Сначала я решил попробовать Hibernate, так как он один из самых распространенных ORM для Java. Но у меня не получилось собрать native image с использованием Hibernate из-за ошибки сборки:

Error: Field java.lang.reflect.Method.defaultValue is not present on type java.lang.reflect.Constructor. Error encountered while analysing java.lang.reflect.Method.getDefaultValue() 
Parsing context:
	parsing org.hibernate.annotations.common.annotationfactory.AnnotationProxy.getAnnotationValues(AnnotationProxy.java:63)
	parsing org.hibernate.annotations.common.annotationfactory.AnnotationProxy(AnnotationProxy.java:52)
...

Есть соответствующий открытый issue: [native-image] Micronaut + Hibernate results in Error encountered while analysing java.lang.reflect.Method.getDefaultValue().

jOOQ


Дальше я решил попробовать jOOQ. Мне удалось собрать нативный бинарник, правда при этом пришлось сделать много настроек: указание, какие классы, когда инициализировать (buildtime, runtime), возиться с рефлекшеном. В итоге все уперлось в то, что при запуске приложения jOOQ инициализирует прокси org.jooq.impl.ParserImpl$Ignore как статический член класса org.jooq.impl.Tools. А данный прокси использует MethodHandle, которые Substrate VM пока не поддерживает. Вот похожий открытый issue: [native-image] Micronaut + Kafka fails to build native image with MethodHandle argument could not be reduced to at most a single call.

Apache Cayenne


Apache Cayenne менее распространен, но выглядит достаточно функциональным. Попробую его подключить. Я создал XML файлы описания схемы базы данных, их можно создать как вручную, так и с помощью CayenneModeler GUI tool, либо на основе уже существующей базы данных. С помощью cayenne-maven-plugin в pom файле будет осуществляться кодогенерация классов, которые соответствуют таблицам базы данных:

cayenne-maven-plugin
<plugin>
    <groupId>org.apache.cayenne.plugins</groupId>
    <artifactId>cayenne-maven-plugin</artifactId>
    <version>${cayenne.version}</version>
    <configuration>
        <map>src/main/resources/db/datamap.map.xml</map>
        <destDir>${project.build.directory}/generated-sources/cayenne</destDir>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>cgen</goal>
            </goals>
        </execution>
    </executions>
</plugin>


Затем я добавил класс CayenneRuntimeFactory для инициализации фабрики контекстов работы с БД:

CayenneRuntimeFactory.java
@Factory
public class CayenneRuntimeFactory {

    private final DataSource dataSource;

    public CayenneRuntimeFactory(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    @Singleton
    public ServerRuntime cayenneRuntime() {
        return ServerRuntime.builder()
                .dataSource(dataSource)
                .addConfig("db/cayenne-test.xml")
                .build();
    }
}


Контроллер HelloController:

HelloController.java
@Controller("/hello")
public class HelloController {

    private final ServerRuntime cayenneRuntime;

    public HelloController(ServerRuntime cayenneRuntime) {
        this.cayenneRuntime = cayenneRuntime;
    }

    @Get("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public HttpResponse<String> hello(String name) {
        final ObjectContext context = cayenneRuntime.newContext();

        final List<User> result = ObjectSelect.query(User.class).select(context);
        if (result.size() > 0) {
            result.get(0).setName(name);
        }

        context.commitChanges();

        return HttpResponse.ok(result.stream()
                .map(x -> MessageFormat.format("{0}.{1}", x.getObjectId(), x.getName()))
                .collect(Collectors.joining(",")));
    }
}


Потом запустил программу как обычный jar, с использованием агента agentlib:native-image-agent, для сбора информации об использованных ресурсах и рефлекшене.

Собрал нативный бинарник, запускаю, перехожу по адресу http://localhost:8080/hello/user и получаю ошибку:

{"message":"Internal Server Error: Provider com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl not found"}

оказывается agentlib:native-image-agent не обнаружил использования этого класса в рефлекшене.

Вручную добавил его в файл reflect-config.json:

{
  "name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl",
  "allDeclaredConstructors":true
}

Снова собираю бинарник, запускаю, обновляю web-страничку и получаю другую ошибку:

Caused by: java.util.MissingResourceException: Resource bundle not found org.apache.cayenne.cayenne-strings. Register the resource bundle using the option -H:IncludeResourceBundles=org.apache.cayenne.cayenne-strings.

Тут все понятно, добавляю настройку, как указано в предлагаемом решении. Опять собираю бинарник (это 5 минут времени), снова запускаю и снова ошибка, другая:

No DataMap found, can't route query org.apache.cayenne.query.SelectQuery@2af96966[root=class name.voyachek.demos.nativemcp.db.User,name=]"}

С этой ошибкой пришлось повозиться, после многочисленных тестов, изучения исходников стало понятно что причина ошибки кроется в этой строчке из класса org.apache.cayenne.resource.URLResource:

return new URLResource(new URL(url, relativePath));

Как оказалось, Substrate VM загружает ресурс по url, который указывается в качестве базового, а не по url, который должен формироваться на основе базового и relativePath. О чем был мною зарегистрирован следующий issue: Invalid resource content when using new URL(URL context, String spec).

Ошибка определена, теперь нужно искать варианты обхода. К счастью Apache Cayenne оказался достаточно настраиваемой штукой. Нужно было зарегистрировать свой собственный загрузчик ресурсов:

ServerRuntime.builder()
                .dataSource(dataSource)
                .addConfig("db/cayenne-test.xml")
                .addModule(binder -> {
                    binder.bind(ResourceLocator.class).to(ClassLoaderResourceLocatorFix.class);
                    binder.bind(Key.get(ResourceLocator.class, Constants.SERVER_RESOURCE_LOCATOR)).to(ClassLoaderResourceLocatorFix.class);
                })
                .build();

Вот его код:

ClassLoaderResourceLocatorFix.java
public class ClassLoaderResourceLocatorFix implements ResourceLocator {

    private ClassLoaderManager classLoaderManager;

    public ClassLoaderResourceLocatorFix(@Inject ClassLoaderManager classLoaderManager) {
        this.classLoaderManager = classLoaderManager;
    }

    @Override
    public Collection<Resource> findResources(String name) {
        final Collection<Resource> resources = new ArrayList<>(3);

        final Enumeration<URL> urls;
        try {
            urls = classLoaderManager.getClassLoader(name).getResources(name);
        } catch (IOException e) {
            throw new ConfigurationException("Error getting resources for ");
        }

        while (urls.hasMoreElements()) {
            resources.add(new URLResourceFix(urls.nextElement()));
        }

        return resources;
    }

    private class URLResourceFix extends URLResource {

        URLResourceFix(URL url) {
            super(url);
        }

        @Override
        public Resource getRelativeResource(String relativePath) {
            try {
                String url = getURL().toString();
                url = url.substring(0, url.lastIndexOf("/") + 1) + relativePath;

                return new URLResource(new URI(url).toURL());
            } catch (MalformedURLException | URISyntaxException e) {
                throw new CayenneRuntimeException(
                        "Error creating relative resource '%s' : '%s'",
                        e,
                        getURL(),
                        relativePath);
            }
        }
    }
}


В нем строчка

return new URLResource(new URL(url, relativePath));

заменена на:

String url = getURL().toString();
url = url.substring(0, url.lastIndexOf("/") + 1) + relativePath;
return new URLResource(new URI(url).toURL());

Собираю бинарник (70 МБ), запускаю, перехожу на http://localhost:8080/hello/user и все работает, на страницу выводятся данные из базы данных.

Ссылка на репозиторий: Github: native-micronaut-cayenne-demo.

Выводы


Поставленная цель достигнута: разработано простое web приложение с доступом к базе данных с помощью ORM. Приложение собрано в нативный бинарник и может запускаться на системах без установленной Java машины. Несмотря на многочисленные проблемы, мною было найдена комбинация фреймворков, настроек и обходных путей, которая позволила получить работающую программу.

Да, возможность собирать обычные бинарники из исходного кода Java пока еще находится в экспериментальном статусе. Это видно по обилию проблем, необходимости искать обходные пути. Но в итоге все равно получилось добиться желаемого результата. Что же я получил?

  • Единственный самодостаточный файл (почти, есть зависимости на библиотеки такие как libc), способный запускаться на системах без Java машины.
  • Время старта в среднем 40 миллисекунд против 2 секунд при запуске обычного jar.

Из недостатков хочется отметить большое время компиляции нативного бинарника. У меня оно занимает в среднем пять минут, и скорее всего, будет увеличиваться при написании кода и подключении библиотек. Поэтому создавать бинарники имеет смысл уже на основе полностью отлаженного кода. К тому же отладочная информация для нативных бинарников доступна только в коммерческой редакции Graal VM — Enterprise Edition.

Комментарии (15)


  1. maxzh83
    06.06.2019 10:02

    Как эксперимент, это наверное интересно, но пока не понимаю где можно применить полученные бонусы (быстрый старт и запуск без установленной java). В той области, где традиционно сильна Java, это все не настолько критично, особенно наличие установленной java. А вот как такое приложение будет работать под нагрузкой, как его мониторить и т.д. это вот куда важнее.


    1. voyachek Автор
      06.06.2019 10:26

      Я вижу себе это так: расширение области применения языка Java. Действительно, там где сейчас Java сильна, особо нет смысла делать ее бинарной. А вот чтобы раздвинуть границы этой области — возможность создавать бинарники — сильный аргумент. Вот есть Golang, он так умеет с самого начала, и на нем написаны замечательные программы: Consul, Docker в которых отсутствие требования установки какой то виртуальной машины скорее плюс чем минус.


      1. andrus_a
        08.06.2019 14:36

        Я вот тоже думаю / работаю в этом направлении. Graal вдруг открыл возможности создавать на Java всевозможные "tools". Наша команда, пишущая Bootique.io, долго думала, как сделать нативную консоль для генерации и управления Bootique проектами. java -jar bq.jar выглядело как то криво. Пару недель назад попробовали Graal, и voila: bq — простой, ~ легкий (30MB) бинарник с шаблонами, полным Bootique функционалом, и т.д. До сих пор воюем с Windows сборкой правда, из-за jline библиотеки, использующей JNI.


    1. ladutsko
      06.06.2019 16:35

      десктопные приложения а-ля ide, admin, etc.
      по поводу мониторинга — думаю jmx будет также доступен на портах и все остальное по аналогии… при имплементации пуш-подхода вообще ничего не поменяется.
      быстрое время старта будет полезно в serverless…
      ps главное чтобы работало стабильно и не отставала от актуальной версии


      1. maxzh83
        06.06.2019 17:04

        десктопные приложения а-ля ide, admin, etc.

        ide, написанные на java можно пересчитать по пальцам (возможно даже одной руки), при этом основная масса пользователей используют их для разработки… на Java, т.е. все равно ставить придется.
        Ну и к тому же, это все профессиональные инструменты (как и админки), а для такого нет большой проблемы поставить jvm.


        1. voyachek Автор
          06.06.2019 17:21

          Существование такой возможности, как standalone приложения, открывает некоторые горизонты, куда ява особо и не ходила: инструменты такого класса как докер, утилиты с мгновенным запуском, то есть туда где обычно писались программы на C/C++, или на Go. Можно конечно выучить эти языки, а можно и остаться на знакомом. Ведь не заставишь же пользователя небольшой утилиты устанавливать ради нее какую то яву машину, выяснять совместимость ява машин разных версий и запускаемой утилиты. Да, есть вариант распространять яву машину с утилитой, это уже не простой бинарник будет, а набор файлов, какой то скрипт запуска и JIT все равно потребуется, и при запуске маленькой программы все равно есть ощутимые задержки, пусть даже потом она отработает мгновенно.


          1. maxzh83
            06.06.2019 17:41

            Да, тут согласен, но это как раз слабая на данный момент сторона Java. Вопрос в том, выстрелит это или нет. Тут есть несколько потенциальных трудностей: удовлетворит ли «пользователя небольшой утилиты» размер этой утилиты, при этом надо понимать, что каждая такая утилита будет тащить с собой свой «домик» примерно как сейчас это делают приложения на electron; удовлетворит ли разработчиков скорость сборки бинарника (например, для тестирования) и насколько удобными будут инструменты генерации под все платформы и т.д.


            1. voyachek Автор
              06.06.2019 18:29

              Вот с «домиком» тут интересная штука. Разработчики SubstrateVM утверждают, что ничего лишнего туда не попадает, следствие этого — необходимость указывать случаи рефлекшена и включаемые ресурсы. То есть сборщик просматривает весь граф выполнения (из-за этого долгая сборка) и включает туда только тот код который может выполниться из программы, ничего лишнего. Сборка программы которая печатает «привет мир» занимает 30 секунд и весит 2.5 МБ. То есть какого-либо обязательного «домика» в десятки мегабайтов, как электрон, она не таскает. Да, программа с подключением двух фреймворков и JDBC драйвера уже весит 70МБ и сборка занимает пять минут, это и на маленькую утилиту она уже не тянет.


              1. maxzh83
                06.06.2019 18:41
                +1

                То есть сборщик просматривает весь граф выполнения (из-за этого долгая сборка)

                Сколько же он будет собирать код какой-нибудь IDEA? Но потенциально возможность крутая конечно


              1. alashkov83
                07.06.2019 14:34

                Сборка программы которая печатает «привет мир» занимает 30 секунд и весит 2.5 МБ

                Это разве мало?


        1. ladutsko
          06.06.2019 18:25
          +1

          ide — это не только Eclipse, NetBeans или IntelliJ IDEA. Это еще например DBeaver и подобные инструменты. Зачем дата- моделеру/админу ставить жаву?!
          Еще пример — админка одно из производителей точек доступа написана на джава. Точками доступа пользуются очень разные люди. Они могут даже не знать, что жава не только кофе…


          1. maxzh83
            06.06.2019 18:47

            это не только Eclipse, NetBeans или IntelliJ IDEA. Это еще например DBeaver и подобные инструменты

            Да, поэтому я и писал про основную массу пользователей. Все таки Eclipse чаще всего используют для Java, а не кастомизуют (DBeaver, 1C и т.д.)
            Зачем дата- моделеру/админу ставить жаву?!

            Разумеется, незачем.
            Я не против таких приложений в принципе, просто рассматриваю насколько эффект стоит затрат.


  1. Kipriz
    06.06.2019 10:32

    Есть ли разница в производительности и потребления памяти/процессора нативного приложения и обычного jar-файла?


    1. voyachek Автор
      06.06.2019 10:40

      Какую-то серьезную проверку я пока не делал. Скорее всего это будет тема следующей статьи. По ощущениям, памяти в бинарнике тратится меньше, хотя бы за счет того что метаданные классов по умолчанию не попадают в бинарник и класс-спейс не занят ими. По процессору думаю будет почти также — если конечно «прогреть» jar, чтобы время на JIT не тратилось. Хотя по пресс-релизам Graal проводит более серьезные оптимизации чем традиционный JIT.


    1. vektory79
      06.06.2019 21:19

      В native-image заявляют скорость ниже, чем у прогретой jvm, но значительно выше, чем у непрогретой.
      Т.е. жто всё-таки не для долгоживущих приложений, а для утилит коммандной строки и т.п.