Газ до отказа — Ожидаемые инструменты и фичи Java 17
24 августа, 2021
Что из себя представляет новая версия Java™? Чтобы ответить на этот вопрос, давайте представим элегантный современный автомобиль легендарной марки, в котором сочетаются верность традициям, комфорт, скорость и новейшие технологии. Не спорим, новые модели машин появляются чуть ли не каждый день, но если вы не хотите выбирать между надежностью и практичностью — с этим авто вы не прогадаете.
Присоединяйтесь к путешествию по новым дорогам, которые открывает Java 17: где бы мы в итоге ни оказались, обещаем, скучно не будет!
У Java™ серьезные намерения
Если вы хотите получить все и сразу: стабильность, безопасность, поддержку, присмотритесь к Java™. На протяжении многих лет разработчики со всего мира (включая инженеров Axiom JDK) вносят изменения в основную ветку OpenJDK. Это значит, что проблемы с безопасностью быстро обнаруживаются и исправляются, а инструменты разработки постоянно улучшаются. Постоянно появляются новые фичи, а устаревшие компоненты удаляются. Java™ до сих пор остается одним из лучших кросс-платформенных языков программирования, а значит — самым надежным вариантом для бизнеса, при этом чем новее версия, тем лучше. Если вы не уверены, стоит ли вам обновляться до последней версии, свяжитесь с нами. Инженеры Axiom JDK проведут анализ вашей архитектуры, и вы получите от них подробное экспертное заключение.
В любом случае, 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 будет входить в сборки для нативных образов, например, Axiom 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, Axiom JDK. 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-ю передачу
Мы решили спросить наших инженеров, что они думают об изменениях в 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™ и лично убедиться в том, насколько ровной и безопасной стала дорога разработки приложений.
А мы, Axiom JDK, с готовностью предоставим вам нашу сборку JDK с открытым исходным кодом, которая включает в себя все новые фичи и не только. Следите за новостями!