Из замечательного интервью на Хабре: «Саймон Риттер — человек, который работал над Java с самого начала и продолжает делать это в роли заместителя технического директора Azul — компании, работающей над виртуальной машиной Zing JVM и одним из лучших сборщиков мусора, C4 (Continuously Concurrent Compacting Collector)»
Ниже — перевод его статьи о новых фичах JDK 12 и некоторых трудностях, с которыми вы можете столкнуться мигрируя на новую сборку.
Я написал несколько постов в блоге, в которых перечислены все изменения для каждого из последних выпусков Java (JDK 10, JDK 11). Сейчас я рассмотрю темную сторону JDK 12, сосредоточив внимание на некоторых подводных камнях, которые могут вызвать проблемы если вы захотите перенести приложение на эту версию.
В JDK 12 наименьшее количество новых функций из всех выпусков Java на сегодняшний день(я насчитал 109 в JDK 10 и 90 в JDK 11). Это не плохо — из-за релизных циклов некоторые версии будут содержать больше изменений, а некоторые — меньше.
Я разобью новые функции на очевидные логические области: язык Java, библиотеки, JVM и другие функции JDK.
Изменения в языке
Функцией, которую я(и я предполагаю, что многие другие люди) будут считать наиболее заметной в JDK 12, является новый оператор switch (JEP 325). Это также первое изменение языка, которое будет использовано в качестве функции для "предварительного просмотра". Идея "предварительного просмотра" была представлена ??в начале 2018 года в рамках JEP 12. По сути, это способ включения бета-версий новых функций используя опции командной строки. Используя предварительный просмотр все еще возможно вносить изменения основанные на обратной связи с пользователем, и в крайнем случае полностью удалить функцию, если она не была принята должным образом. Ключевым моментом в функциях предварительного просмотра является то, что они не включены в спецификацию Java SE. Про новый switch есть очень хороший перевод на Хабре.
В JDK 12 switch стал выражением, которое оценивает свое "содержимое" для получения результата. Сразу поясню, что это не влияет на обратную совместимость, поэтому вам не нужно изменять какой-либо код, который использует switch в качестве оператора.
Я буду использовать пример из JEP, так как он простой и понятный:
int numLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Huh? " + day);
}
Как видите, мы сопоставляем день недели с названием переменной day
, потом присваиваем значение numLetters
. Теперь, когда switch является оператором, мы можем сделать присвоение один раз (значительно уменьшая вероятность ошибочного кода), используя результат оператора switch:
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Huh? " + day);
};
Вы быстро заметите два изменения в синтаксисе. Разработчики OpenJDK наткнулись на малоизвестную синтаксическую функцию, называемую «список через запятую». Также оператор лямбда-выражения ->
упрощает возвращение значения. Вы все еще можете использовать break
со значением, если вы действительно хотите этого. Есть несколько других деталей об этой функции, но, вероятно, легче прочитать JEP.
Библиотеки
Есть одно изменение, которое я считаю очень полезным. Также есть и ряд второстепенных.
teeing collector
В Streams API, как обычно, появился новый Collector, предоставляемый служебным классом Collectors. Новый коллектор можно получить с помощью метода teeing()
. teeing-коллектор принимает три аргумента: два коллектора и бифункцию. Для понимания работы этого коллектора рекомендую эту статью на Хабре.
Чтобы понять, как он это делает, я нарисовал диаграмму:
Все значения из входного потока передаются в каждый коллектор. Результат каждого коллектора передается в качестве аргументов в BiFunction для и генерации окончательного результата.
Простым примером является вычисление среднего значения (да, я знаю, что уже есть коллекторы для этого, например averagingInt()
, но это простой пример, чтобы помочь понять концепцию).
/* Assume Collectors is statically imported */
double average = Stream.of(1, 4, 2, 7, 4, 6, 5)
.collect(teeing(
summingDouble(i -> i),
counting(),
(sum, n) -> sum / n)
);
Первый коллектор вычисляет сумму входного потока, а второй — количество элементов. BiFunction делит сумму на количество элементов, чтобы получить среднее значение.
java.io
InputStream skipNBytes(long n)
— пропускает и отбрасывает ровно n байтов с входного потока InputStream. Если n равно нулю или меньше, байты не пропускаются.
java.lang
Появился новый пакет, java.lang.constant, который является частью API constant JVM, JEP 334.
Каждый файл класса Java имеет постоянный пул, в котором хранятся операнды для инструкций байт-кода в классе. Разработчикам сложно манипулировать файлами классов из-за проблем с загрузкой классов. API constant JVM предоставляет символические ссылочные типы для описания каждой формы константы (класса, загружаемой константы, MethodHandle
, MethodHandle
константы, MethodType
константы).
Это также повлияло на несколько других классов. У всех следующих классов теперь есть describeConstable()
метод:
- Class
- Double
- Enum
- Float
- Integer
- Long
- String
- MethodHandle
- MethodType
- VarHandle
Как британец, я нахожу это довольно забавным. Термин «Констебль»(Constable, describeConstable
) используется с 11-го века, и именно так мы часто обращаемся к полицейским. Это также имя известного художника 18-го века, Джона Констебла. Это заставляет меня задуматься, будет ли в будущей версии метод describeTurner()
. Очевидно, что в данном случае это сокращение Constant Table
, не относящееся к сотруднику закона или пейзажисту.
Следующие классы теперь включают метод resolveConstantDesc()
:
- Double
- Enum.EnumDesc
- Float
- Integer
- Long
- String
java.lang.Character
Внутренние классы были обновлены, чтобы включить новые блоки Unicode. Мне всегда нравится видеть то, что нашли люди чтобы добавить в Unicode, вот несколько примеров:
- Шахматные символы
- Цифры майя
- Согдийский — восточный иранский язык, который перестал использоваться в 11-ом веке.
- Старый согдийский — более старый (и, я подозреваю, еще более ограниченный) вариант согдийского
java.lang.Class
arrayType()
возвращает Class
для типа массива, тип компонента которого описывается этим Class
. Это можно проверить используя jshell
:
jshell> (new String[2]).getClass().getName()
$11 ==> "[Ljava.lang.String;"
jshell> (new String[2]).getClass().arrayType()
$12 ==> class [[Ljava.lang.String;
jshell> "foo".getClass().arrayType()
$15 ==> class [Ljava.lang.String;
Я не совсем уверен, в чем смысл этого метода, так как все, что он делает — это добавляет Class
к тому типу, который представляет этот класс.
componentType()
, такой же, как и getComponentType()
. Напрашивается вопрос — зачем добавлять избыточный метод?
descriptorString()
— опять же, возвращает тот же результат, что и getName()
. Однако он необходим, поскольку Class
теперь реализует TypeDescriptor
интерфейс, связанный с новым API constant JVM.
lava.lang.String
indent()
— добавляет ряд начальных пробелов в строку. Если параметр отрицателен, то это количество начальных пробелов будет удалено(если это возможно).
transform()
— Применяет предоставленную функцию к строке. Результат может не быть строкой.
java.lang.invoke
У VarHandle
теперь есть toString()
для возврата компактного описания.
У java.net.SecureCacheResponse
и у java.net.ssl.HttpsConnection
есть новый метод, getSSLSession()
который возвращает Optional
, содержащий SSLSession
, используемый в соединении.
java.nio.files
У класса Files
есть новый метод, mismatch()
, который находит и возвращает позицию первого несовпадающего байта в содержимом двух файлов или -1L, если нет несоответствия.
java.text
Есть новый класс CompactNumberFormat
. Это подкласс NumberFormat
, который форматирует десятичное число в компактной форме. Примером компактной формы — 1M
вместо 1000000
, таким образом — требует два вместо девяти символов. NumberFormat
и java.text.spi.NumberFormatProvider
были расширены, чтобы включить новый метод getCompactNumberInstance()
. Существует также новый enum, NumberFormatStyle
который имеет два значения: LONG и SHORT.
java.util.concurrent
CompletionStage теперь включает несколько перегруженных форм с тремя методами:
- exceptionallyAsync
- exceptionallyCompose
- exceptionallyComposeAsync
Эти методы расширяют возможности создания нового CompletionStage
из существующего, CompletionStage
если текущий завершается исключением. Проверьте документацию API для деталей.
javax.crypto
Класс Cipher
имеет новый метод toString()
, который возвращает строку, содержащую преобразование, режим и поставщика Cipher
.
javax.naming.ldap.spi
Это новый пакет в JDK 12 и он содержит два класса — LdapDnsProvider
, который является классом поставщика услуг для поиска DNS при выполнении операций LDAP, и LdapDnsProviderResults
который инкапсулирует результат поиска DNS для URL-адреса LDAP.
Swing
Swing все еще обновляется! Да, filechooser.FileSystemView
теперь имеет новый метод getChooserShortcutPanelFiles()
. Он возвращает массив файлов, представляющих значения для отображения по умолчанию на панели ярлыков выбора файлов.
Изменения в JVM
JEP 189: Shenandoah: Low-Pause-Time сборщик мусора
Shenandoah — это исследовательский проект, анонсированный Red Hat в 2014 году, который нацелен на требования приложений с малой задержкой для управления памятью в JVM. Его цели — максимальное время паузы 1..10мс для кучи более 20 Гб (поэтому он не предназначен для небольших приложений — как ответил один из разработчиков Shenandoah, это не так и он отлично справляется с небольшими приложениями). Этот коллектор спроектирован так, чтобы работать параллельно с потоками приложения, поэтому избегайте проблем, которые мы видим в большинстве сборщиков мусора.
JEP 344: смешанные коллекции G1
Это изменение предназначено для того, чтобы улучшить поведение сборщика G1 при достижении заданной цели задержки. G1 делит пространство кучи (как старое, так и старое) на регионы. Идея заключается в том, что в старом поколении не нужно собирать мусор за одну операцию. Когда G1 необходимо собрать мусор, он выбирает регионы, которые он определяет. Это называется набором сбора. До JDK 12, когда начиналась работа над набором все работы должны были быть завершены, по сути, как атомарная операция. Проблема заключалась в том, что иногда из-за изменений в использовании приложением пространства кучи, набор сбора оказывался слишком большим и занимал слишком много времени для сбора, что приводило к тому, что время паузы не достигалось.
В JDK 12, если G1 идентифицирует эту ситуацию, то прервет сбор данных на полпути, если это не повлияет на способность приложения продолжать выделять пространство для новых объектов. Чистый эффект G1 будет лучше при достижении небольшого времени паузы.
JEP 346: быстро вернуть неиспользованную выделенную память из G1
Это еще одно улучшение производительности для G1, но еще одно связано с тем, как JVM взаимодействует с остальной частью системы. Очевидно, что для кучи JVM требуется память, и при запуске она запрашивает память у распределителя виртуальной памяти операционной системы. При запуске приложения могут быть случаи, когда объем памяти, необходимый для кучи, падает, и часть выделенной памяти может быть возвращена операционной системе для использования другими приложениями.
G1 уже делает это, но может делать это только в одном из двух мест. Во-первых, во время полного сбора, а во-вторых, во время одного из параллельных циклов. G1 старается не делать полный сбор, и при низком использовании памяти между циклами сбора могут быть значительные периоды. Это приводит к тому, что G1 может сохранять зафиксированную память в течение длительного времени.
В JDK 12 G1 во время бездействия приложения будет периодически пытаться продолжить или запустить параллельный цикл для определения общего использования кучи Java. Неиспользованная память может быть возвращена операционной системе более своевременным и предсказуемым образом.
Новый флаг командной строки -XX:G1PeriodicGCInterval
может быть использован для установки количества миллисекунд между проверками.
Эта функция приведет к более консервативному использованию памяти JVM для приложений, которые простаивают в течение длительных периодов времени.
Другие новые функции JDK
JEP 230: набор микробенчмаркинга
Java Microbenchmarking Harness (JMH) был разработан Алексеем Шипилевым, когда он работал в Oracle, и предоставляет обширную платформу для разработки тестов производительности для приложений Java. Алексей проделал выдающуюся работу, помогая людям избежать многих простых ошибок, которые они допускают, пытаясь проанализировать производительность приложений: прогревать, избегать исключений, и т. д.
Теперь JMH может быть включено в OpenJDK. Любой, кто интересуется работой над самим JDK и изменением кода, может использовать это для сравнения производительности как до, так и после их изменений, а также для сравнения производительности в разных выпусках. Ряд тестов включены, чтобы включить тестирование; Дизайн JMH таков, что легко добавлять новые тесты туда, где это необходимо.
JEP 340: один порт Aarch64, а не два
OpenJDK имеет два порта для архитектуры Arm64, один из которых предоставлен Oracle, а другой — Red Hat. Поскольку в этом не было необходимости, и Oracle перестала поддерживать Arm для своих двоичных файлов JDK, было принято решение использовать только порт Red Hat, который все еще поддерживается и развивается.
JEP 341: архивы CDS по умолчанию
Класс Data Sharing (CDS) раньше был коммерческой функцией в Oracle JDK. С недавним переходом, выполненным в JDK 11, для устранения всех функциональных различий между Oracle JDK и OpenJDK, его включили в OpenJDK.
Для использования CDS требуется архив, созданный для классов, которые загружаются при запуске приложения. В JDK 12 для 64-разрядных платформ теперь есть файл classes.jsa
в каталоге lib/server
. Это архив CDS для «классов по умолчанию». Я предполагаю, что он означает все открытые классы в модулях JDK; Я не мог найти способ распаковать его, чтобы проверить. Поскольку CDS включен по умолчанию, что эквивалентно -Xshare:auto
опции в командной строке, пользователи получат выгоду от улучшенного времени запуска приложений из него.
Выводы
JDK 12 предоставляет небольшое количество новых функций и API, причем выражение switch
является наиболее интересным для разработчиков. Пользователи G1, несомненно, оценят улучшения производительности.
С новой версией релиза я бы посоветовал всем пользователям тестировать свои приложения в этом выпуске. Отслеживание постепенных изменений позволит избежать неожиданностей, если вы решите перейти к следующему выпуску долгосрочной поддержки.
У нас есть бесплатные сборки JDK 12 для Zulu Community Edition, которые помогут вам в тестировании. Обязательно попробуйте их.
Комментарии (15)
transcengopher
03.04.2019 14:50+1transform() — Применяет предоставленную функцию к строке. Результат не должен быть строкой.
Подчёркнутое — ошибка. Результат может не быть строкой, но может и быть.
Strign foobar = "foo".transform(s -> s + "-bar");
является полностью "легальным" использованием.
Так же как и
char[] chars = "foo".transform(s -> s.chars().filter(c -> c >= 103).toArray());
sergey-gornostaev
03.04.2019 16:59+1Его цели — максимальное время паузы 100 мс для кучи более 20 Гб (поэтому он не предназначен для небольших приложений).
Разработчик Shenandoah с этим не согласен.
Anton23 Автор
03.04.2019 19:55Да, я подправил это место. Еще в телеграме olegchir писал, что Саймон тоже самое говорил на своем интервью.
sergey-gornostaev
03.04.2019 20:08Тогда и про 100 мс надо исправлять.
Anton23 Автор
03.04.2019 20:49Появилась новая информация. Алексей Шипилев написал, что Саймон исправил свое сообщение:
Ah, OK, my bad. I just looked at the presentation where I got those figures and it dates back to 2017. I will change my blog. How small a heap would you suggest Shenandoah works with?
— Simon Ritter (@speakjava) April 3, 2019
Там интересно всю ветку почитать. Добавлю информацию в статью.
relgames
04.04.2019 09:59А есть ли люди, которые в проде используют Java выше 8?
Откуда вы ее берете? Как скоро там выходят критические патчи?
Читать посты про новые фичи Java интересно, но мало практической информации, к сожалению.
Anton23 Автор
04.04.2019 10:02А есть ли люди, которые в проде используют Java выше 8?
Да вроде есть
Откуда вы ее берете?
Эм, OpenJDK, OracleJDK, Red Hat(вроде), короче много вендоров выпускают свои сборки JDK.
Как скоро там выходят критические патчи?
Ну точно быстрее чем на 8ку.
Читать посты про новые фичи Java интересно, но мало практической информации, к сожалению.
Какой именно? Если мало — читайте JEP`ы, в статье ссылки есть.
transcengopher
04.04.2019 17:09А есть ли люди, которые в проде используют Java выше 8
В нашем проекте главная ветка уже перешла на 11, и идут работы по переходу на 12. Мы делаем именно релизы, так что это пока что не прод, но всё к тому движется.
Откуда вы ее берете?
Глянул сейчас — похоже, что таки у Oracle, с jdk.java.net, но версию именно OpenJDK. Про патчи — вот честно, понятия не имею, насколько скоро. Прошу за это прощения. В настоящее время версия с сайта датируется 19м марта 2019, но мне неизвестно, есть ли там сейчас какие-то критические проблемы. Замечу, впрочем, что Java теперь в основном open-source — и, следственно, с патчами будет как там принято. Но есть и платные дистрибутивы, там наверняка можно будет по контракту с кого-нибудь стрясти заплатку в экстренном режиме.
sergey-gornostaev
04.04.2019 21:31Как скоро там выходят критические патчи?
Минорные обновления для билдов OpenJDK от Oracle только раз в квартал и не более двух для каждой мажорной версии Java. Другие поставщики могут выпускать обновления чаще и дольше, но на бесплатной основе никто ничего не гарантирует.
puyol_dev2
java.lang.Character — самая полезная фича! Теперь можно считать по майски и писать на согдийском
Anton23 Автор
А то! Следующая после Character фича — Swing.
striver
А вдруг они одумаются и в 13-й версии выкатят новый FX!
fshp
Fx и так обновляется активно. Просто как сторонняя библиотека.