Фичи и инструменты Java 17

Газ до отказа — Ожидаемые инструменты и фичи Java 17


24 августа, 2021


Что из себя представляет новая версия Java™? Чтобы ответить на этот вопрос, давайте представим элегантный современный автомобиль легендарной марки, в котором сочетаются верность традициям, комфорт, скорость и новейшие технологии. Не спорим, новые модели машин появляются чуть ли не каждый день, но если вы не хотите выбирать между надежностью и практичностью — с этим авто вы не прогадаете.

Присоединяйтесь к путешествию по новым дорогам, которые открывает Java 17: где бы мы в итоге ни оказались, обещаем, скучно не будет!

У Java™ серьезные намерения

Если вы хотите получить все и сразу: стабильность, безопасность, поддержку, присмотритесь к Java™. На протяжении многих лет разработчики со всего мира (включая инженеров BellSoft) вносят изменения в основную ветку OpenJDK. Это значит, что проблемы с безопасностью быстро обнаруживаются и исправляются, а инструменты разработки постоянно улучшаются. Постоянно появляются новые фичи, а устаревшие компоненты удаляются. Java™ до сих пор остается одним из лучших кросс-платформенных языков программирования, а значит — самым надежным вариантом для бизнеса, при этом чем новее версия, тем лучше. Если вы не уверены, стоит ли вам обновляться до последней версии, свяжитесь с нами. Инженеры BellSoft проведут анализ вашей архитектуры, и вы получите от них подробное экспертное заключение.

В любом случае, c Java™ вы не ошибетесь.

Пристегните ремни — сейчас мы узнаем, что версия грядущая нам готовит.

Счастливое число 17

В современном автомобиле не должно быть устаревших конструктивных элементов и ненужных деталей, которые только ограничивают его скорость. При этом ему следует оставаться удобным и мощным. Этих принципов придерживается и JDK-сообщество. Новая версия — это релиз с долгосрочной поддержкой (LTS): она будет стандартной поддерживаемой версией на протяжении многих лет. Ожидается, что поддержка Java 17 будет осуществляться до 2026 года. Как это обычно бывает в случае LTS-релиза, некоторые фичи были признаны устаревшими, некоторые были добавлены, а наиболее важные — усовершенствованы.

Давайте пройдемся по всем изменениям в Java 17.

Добавленные фичи

Новые компоненты для более мощного движка.

  • Контекстные фильтры десериализации (JEP 415) Данная функция предназначена для конфигурации фильтров посредством фабрики фильтров для всей JVM. Разработчики могут выбрать подходящий фильтр для конкретной операции десериализации. Фильтры десериализации были включены в Java 9. Их целью было обеспечение проверки входящих потоков данных со стороны кода приложения и библиотеки перед их сериализацией. Новый контекстный подход позволит значительно повысить безопасность благодаря тому, что в процессе десериализации можно будет удалить потенциально вредоносный код.
  • Foreign Function & Memory API (JEP 412) призван заменить нативный интерфейс Java™ (JNI) на более продвинутую модель, разработанную специально для Java™. Этот API позволит улучшить производительность и безопасность благодаря дефолтному запрету на выполнение небезопасных операций. Также можно будет по-разному работать с памятью вне кучи. Ожидается, что в будущем эта фича позволит использовать внешние функции, написанные и на других языках помимо С. Тем не менее JNI пока не был удален, поэтому приложения, которые зависят от JNI, будут нормально запускаться в Java 17.
  • Восстановление всегда строгой семантики с плавающей запятой (JEP 306) позволит сделать семантику операций с плавающей запятой, какой она и была в Java™ до версии 12. В 1990-х у архитектуры х86 были определенные ограничения, в связи с чем было принято решение об изменении дефолтной семантики с плавающей запятой. Позже было разработано расширение SSE2; поскольку оно поддерживается современными процессорами, проблемы семантики с плавающей запятой больше не существует. Это значит, что семантику операций с плавающей запятой можно сделать всегда строгой, что упростит разработку библиотек, чувствительных к числовым значениям, включая Java.lang.Math и java.lang.StrictMath.
  • Pattern matching для switch (JEP 406) был добавлен в качестве preview. Данная фича позволяет тестировать выражения по ряду шаблонов, каждый из которых выполняет определенное действие. Были представлены два новых шаблона. Все существующие выражения и операторы switch также будут компилироваться без проблем.
  • Новый конвейер рендеринга для macOS (JEP 382) будет основан на Apple Metal API вместо старого OpenGL. Это особенно важно, учитывая тот факт, что Apple, вполне вероятно, удалит OpenGL API из будущих версий macOS. Конвейер соответствует существующей модели 2D-конвейера Java™ и будет как минимум таким же эффективным, как и текущий конвейер рендеринга.

Удаленные фичи:

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

  • Удаление экспериментального компилятора AOT и JIT (JEP 410). Компилятор ahead-of-time (AOT) и just-in-time (JIT) был не так давно добавлен в Java™ в качестве эксперимента, но не пользовался особой популярностью. Поскольку никто особо не заметил его отсутствия в JDK 16, было решено окончательно удалить его вместе со следующими модулями: jdk.aot (инструмент jaotc), internal.vm.compiler (компилятор Graal), а также jdk.internal.vm.compiler.management (Graal MBean). Следует отметить, что хотя Graal был исключен из Java 17, он все еще необходим для реализации многих фич, поэтому его разработка будет продолжена. Graal будет входить в сборки для нативных образов, например, Liberica Native Image Kit.
  • Удаление механизма активации удаленного вызова метода (RMI) (JEP 407) с сохранением всех остальных компонентов RMI. Механизм активации помечен как устаревший и выведен из употребления.
  • Applet API объявлен устаревшим и будет в дальнейшем удален (JEP 398). Поскольку почти все браузеры удалили поддержку плагинов Java™ или планируют в скором времени это сделать, с этим, теперь уже бесполезным API придется попрощаться. На самом деле, некоторые приложения до сих пор зависят от этого API, поэтому в Java 17 он все еще присутствует. Если данный API вам необходим, возможно, будет лучше остаться на Java 8 (поддерживается до 2022 г.) или 11 (поддерживается до 2023 г.)
  • Security Manager объявлен устаревшим (JEP 411) и будет удален в будущем релизе. Эта фича сослужила добрую службу: она была представлена еще в Java 1.0 и в основном использовалась для обеспечения безопасности кода со стороны клиента. Ожидается, что для выполнения этой задачи будут использоваться современные технологии обеспечения безопасности. Проблема в том, что Security Manager до сих пор используется во многих приложениях. Из-за этого придется переписать огромное количество кода, когда фичу уберут из Java 18. Так как по умолчанию значение системного свойства java.security.manager теперь «disallow», большинство тестов, вызывающих System.setSecurityManager() нужно будет запускать с указанием -Djava.security.manager=allow.

Усовершенствованные фичи

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

  • Vector API (JEP 414) был представлен в предыдущей версии в режиме инкубатора. Ввиду положительного отклика разработчиков этот API был улучшен с точки зрения производительности. Он поддерживает операции с символами, трансцедентные и тригонометрические линейные операции (lanewise) на базе x64 с применением библиотеки Short Vector Math Library (SVML) Intel. Кроме того, интерфейс обеспечивает более эффективное преобразование байтовых векторов в массивы типа boolean и наоборот. Ниже мы более подробно рассмотрим характеристики этой фичи.
  • Strong encapsulation for all JDK internals (JEP 403) за исключением критических внутренних API. Суть данной фичи заключается в запрете на ослабление строгой инкапсуляции внутренних компонентов посредством одного параметра командной строки. Это, в свою очередь, повысит безопасность и вынудит разработчиков использовать стандартные API вместо внутренних компонентов. В результате обновление до новых версий Java™ станет гораздо проще. Однако есть и небольшой недостаток: если приложение или фрэймворк зависят от от внутреннего API, придется переписать много кода или вообще не переходить на Java 17. Следует отметить, что классы sun.misc.Unsafe и reflect а также флаг add-opens все еще присутствуют в Java™, и в дальнейшем можно продолжить их использование.
  • Закрытые классы и интерфейсы (JEP 409) ограничивают их расширение или реализацию посредством указания тех классов или интерфейсов, которым это разрешено. Данная фича была ранее представлена в виде предварительной версии. Благодаря ей у разработчиков будет больше контроля над кодом, ответственным за реализацию созданных ими классов.
  • Портирование JDK на MacOS/AArch64 (JEP 391). Перевод компьютеров Mac на архитектуру AArch64 идет в полном разгаре (здесь вы можете почитать нашу статью на эту тему). В связи с этим возникла необходимость в порте для macOS. Порты для Linux и Windows на базе AArch64 уже существуют, поэтому для создания нового порта можно будет использовать условную компиляцию и переписать большую часть уже имеющегося кода.
  • Усовершенствованные генераторы псевдо-случайных чисел (JEP 356) были введены вместе с новым интерфейсом RandomGenerator и универсальным API для всех RNG-алгоритмов, включая три уже существующих.

Пара слов о Vector API

Давайте проведем небольшое исследование, чтобы увидеть Vector API в действии.

В тестовом приложении ниже производится расчет «sin»(double) массива входных данных. Эта функция уже применяется в качестве встроенной функции HotSpot для x86_64. Вопрос: может ли она выполняться еще быстрее при использовании SVML? Для сравнения расчетов «sin» с применением VectorAPI и без него мы используем следующий код, основанный на jmh:

/*
* Copyright (c) 2021, BELLSOFT. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*/

import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;

import jdk.incubator.vector.VectorSpecies;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.DoubleVector;

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(3)
@State(Scope.Thread)

public class VectAPI {

    static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED;

    @Param({"8", "16", "64", "256", "1024", "4096", "16384"})
    int size;

    double[] in;
    double[] out;

    @Setup(Level.Trial)
    public void initArrays() {
        in = new double[size];
        for (int i = 0; i < size; i++) {
            in[i] = i;
        }
        out = new double[size];
    }

    @Benchmark
    @Threads(1)
    public double[] scalarSin() {
        for (int i = 0; i < size; i++) {
            out[i] = Math.sin(in[i]);
        }
        return out;
}

@Benchmark
@Threads(1)
public double[] vectorSin() {
    int i = 0;
    int upperBound = SPECIES.loopBound(size);
    for (; i < upperBound; i += SPECIES.length()) {
        DoubleVector va = DoubleVector.fromArray(SPECIES, in, i);
        DoubleVector vb = va.lanewise(VectorOperators.SIN);
        vb.intoArray(out, i);
        }
    for (; i < size; i++) {
        out[i] = Math.sin(in[i]);
        }
    return out;
    }
}

Запустив тест на процессоре Xeon(R) Platinum 8268 @ 2,90 Ггц, мы получили следующие результаты:

Benchmark               (size)          Mode       Cnt      Score            Error      Units
VectAPI.scalarSin       8               avgt       15       82.495 ±         1.274      ns/op
VectAPI.scalarSin       16              avgt       15       170.337 ±        1.346      ns/op
VectAPI.scalarSin       64              avgt       15       679.862 ±        7.345      ns/op
VectAPI.scalarSin       256             avgt       15       2700.459 ±       34.977     ns/op
VectAPI.scalarSin       1024            avgt       15       11511.768 ±      194.371    ns/op
VectAPI.scalarSin       4096            avgt       15       45569.034 ±      477.101    ns/op
VectAPI.scalarSin       16384           avgt       15       188526.742 ±     1008.427   ns/op
VectAPI.vectorSin       8               avgt       15       12.815 ±         0.284      ns/op
VectAPI.vectorSin       16              avgt       15       22.056 ±         0.788      ns/op
VectAPI.vectorSin       64              avgt       15       76.410 ±         1.329      ns/op
VectAPI.vectorSin       256             avgt       15       297.379 ±        3.158      ns/op
VectAPI.vectorSin       1024            avgt       15       1344.975 ±       51.351     ns/op
VectAPI.vectorSin       4096            avgt       15       5817.973 ±       55.963     ns/op
VectAPI.vectorSin       16384           avgt       15       24212.389 ±      915.261    ns/op

В итоге оказалось, что VectAPI.vectorSin примерно в 8 раз эффективнее VectAPI.scalarSin. Изучив журнал процесса компиляции (-XX:+PrintCompilation), мы обнаружили, что использовался Double512Vector, то есть 8 чисел типа double. Результаты указывают на линейную масштабируемость.

Давайте заглянем под капот. Основное действие — расчет «sin»: код “va.lanewise(VectorOperators.SIN)” для расчета вектора. Он компилируется с целью вызова в итоге библиотеки svml library. libsvml.so входит в состав x86_64 jdk17. Библиотека содержит реализацию нескольких векторных функций для векторов разного размера. Компилятор C2 вставляет вызовы этих функций вместо скалярной реализации по умолчанию, как было бы при наличии libsvml.

Примечание: запуск тестов на более старых системах без поддержки AVX512 (или эмуляция таких систем посредством настроек UseAVX=1 или UseAVX=2 для использования 128- или 256-битных векторов) дает результаты, которые ухудшаются пропорционально уменьшению размера вектора. Как следствие, мы получаем почти идеальный линейный масштаб. Отключение встроенной векторной функции посредством отключения UseVectorStubs с целью запуска встроенной скалярной функции «sin» приводит к чуть более медленному выполнению, чем при использовании скалярной функции, из-за дополнительного потребления ресурсов.

По результатам нашего теста можно утверждать, что Vector API обеспечивает более удобное использование SVML и обладает высокой скоростью. Он адаптируется под возможности процессора (в данном случае — использование векторов разного размера дает линейное увеличение производительности). Скалярные расчеты, выполненные в обход Vector API, тоже сработают, но следует ожидать несколько большего потребления ресурсов.

Переключаемся на 17-ю передачу

Gearbox

Мы решили спросить наших инженеров, что они думают об изменениях в Java 17. Вот, что они ответили:

Наиболее значимые изменения в Java 17 касаются подготовки к удалению Security Manager (ожидается в Java 18), введения строгой инкапсуляции внутренних API и добавления нового Foreign API — интерфейса подключения внешнего кода и памяти к JVM. JVM теперь запускается в строгом режиме, в котором невозможно нарушить изоляцию внутренних классов в API, ввиду чего недоступные модули надежно защищены. Foreign API делает возможной безопасную интеграцию внешнего по отношению к JVM кода и off-heap памяти взамен использования небезопасных механизмов (sun.misc.Unsafe) и JNI. Эти механизмы позволяют вывести систему безопасности JDK на новый уровень.

Сергей Чернышев

Разработчики особо оценят pattern matching для switch, так как эти фичи делают процесс разработки гораздо легче! Код станет более понятным, лаконичным, его будет проще писать и модифицировать. Кроме того, полезным будет и Vector API. В случаях, когда задача хорошо укладывается в векторные операции, можно получить большой прирост производительности.​​ Мне кажется, что введение в Java™ Vector API является предпосылкой для прорыва в следующих версиях.

Дмитрий Стрижикозин

Введение контекстных фильтров десериализации — большой шаг на пути повышения безопасности Java™. Если вы работаете с внешними данными, эти фильтры помогут вам устранить потенциально уязвимые для злоумышленников места. Кроме того, строгая инкапсуляция внутренних элементов JDK способствует более безопасной web-разработке.

Каждый разработчик найдет среди обновлений то, что больше всего придется ему по душе, будь то Vector API для более быстрых расчетов или новые генераторы псевдо-случайных чисел с дополнительными интерфейсами. А API для доступа к внешним функциям и pattern matching для switch упрощают и ускоряют написание кода.

Дмитрий Почепко

Строгая инкапсуляция внутренних компонентов JDK поможет разработчикам избежать ситуаций, в которых сложно понять, как в приложении работает старый код. Она снижает риск использования потенциально небезопасных не задокументированных API без понимания принципа их работы. Не знаю, насколько полезен будет API для доступа к внешним функциям и памяти вне кучи и будет ли действительно настолько просто использовать внешний код. А появление порта Mac Aarch говорит о том, что разработчики Java™ держат руку на пульсе, и это не может не радовать.

Петр Железняков

Хотите узнать больше мнений специалистов по этому и другим вопросам?

Гонка началась!

Java™ всегда была крутым внедорожником — мощным, быстрым, готовым к любым дорогам или бездорожью. С годами она становилась только быстрее и оснащалась полезными фичами, которые должны быть в любом современном языке программирования. Поэтому если вы ждали подходящего момента для перехода на Java™ или обновления версии — момент настал! Через месяц вы сможете сесть за руль обновленной Java™ и лично убедиться в том, насколько ровной и безопасной стала дорога разработки приложений.

А мы, команда BellSoft, с готовностью предоставим вам нашу сборку JDK с открытым исходным кодом, которая включает в себя все новые фичи и не только. Следите за новостями!

Author image

Дмитрий Чуйко

Старший инженер по производительности BellSoft

 Twitter

 LinkedIn

BellSoft LTD [email protected] BellSoft LTD logo Liberica Committed to Freedom 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 111 North Market Street, Suite 300 CA 95113 San Jose US +1 702 213-59-59