Реліз Java 20: останній крок до великого релізу
Цей день настав! Офіційний реліз JDK 20 вже сьогодні (21 березня), вже зараз! А це значить, що треба розібратися, що ж в ньому нового.
Java 20 General-Availability
Реліз JDK 20 є одинадцятим за рахунком Feature Release, який своєчасно випущено в рамках шестимісячної каденції. Такий рівень передбачуваності дозволяє розробникам досягати відносно простого, послідовного та економного за часом впровадження інновацій завдяки постійному потоку очікуваних змін.
Вклад компаній у Java 20
У Java 20 з 2314 проблем JIRA, позначених як виправлені, 1595 було завершено командою Oracle, а 719 було надано іншими членами спільноти Java. Компанія Oracle висловлює подяку розробникам, які працюють в таких організаціях, як Alibaba, Amazon, ARM, Google, Huawei, IBM, Intel, ISCAS, Red Hat, SAP і Tencent, за їх значний внесок. Ми також вдячні за внесок менших організацій, таких як Bellsoft та Loongson, а також незалежних розробників, які в сукупності внесли 7% виправлень у Java 20.
Що нового у Java 20
JDK Enhancement Papers
Отже, почнемо із глобальних змін, які несуть нові JDK Enhancement Papers (JEP).
JEP 429
JEP 429 — API на стадії інкубатора дозволяє обмінюватися незмінними даними всередині потоків і між ними. Їм надається перевага перед локальними змінними потоку, особливо при використанні великої кількості віртуальних потоків. Значення з областю видимості дозволяє безпечно та ефективно обмінюватися даними між компонентами у великій програмі без використання аргументів методу. Цілі включають простоту використання, зрозумілість, надійність та продуктивність.
Розробники традиційно використовують локальні змінні потоку (thread locals), що з’явилися в Java 1.2, щоб допомогти компонентам застосунку обмінюватися даними, коли це саме потрібно. Локальна змінна потоку — це змінна типу ThreadLocal. Незважаючи на те, що вона виглядає як звичайна змінна, вона має декілька втілень, по одному для кожного потоку; конкретне втілення, яке використовується, залежить від того, який потік викликає її методи get()
або set(...)
, щоб прочитати або записати її значення. Код в одному потоці автоматично читає і записує свій екземпляр, тоді як код в іншому потоці автоматично читає і записує свій власний окремий екземпляр. Зазвичай локальна змінна потоку оголошується як кінцеве статичне поле, щоб її можна було легко отримати з багатьох компонентів.
На жаль, локальні змінні потоку мають багато недоліків у дизайні, яких неможливо уникнути:
- Необмежена змінюваність. Кожна локальна змінна потоку є змінюваною: будь-який код, який може викликати метод
get()
локальної змінної потоку, може викликати методset(...)
цієї змінної в будь-який час.ThreadLocal
API дозволяє це, щоб підтримувати повністю загальну модель комунікації, де дані можуть передаватися у будь-якому напрямку між компонентами. Однак це може призвести до спагетіподібного потоку даних і до програм, в яких важко визначити, який компонент оновлює спільний стан і в якому порядку. - Необмежений час життя. Після того, як екземпляр локальної змінної потоку записано за допомогою методу
set(...)
, цей екземпляр зберігається протягом усього часу життя потоку, або доки код у потоці не викличе методremove()
. На жаль, розробники часто забувають викликатиremove()
, тому внутрішньопотокові дані часто зберігаються довше, ніж потрібно. Крім того, у програмах, які покладаються на необмежену змінюваність локальних змінних потоку, може не бути чіткого моменту, коли потоку безпечно викликатиremove()
; це може призвести до довготривалого витоку пам’яті, оскільки дані потоку не буде очищено GC до завершення роботи потоку. - Дорога спадковість. Накладні витрати на локальні змінні потоку можуть бути гіршими при використанні великої кількості потоків, оскільки локальні змінні батьківського потоку можуть бути успадковані дочірніми потоками (локальна для потоку змінна насправді не є локальною для одного потоку). Коли розробник вирішує створити дочірній потік, який успадковує локальні для потоку змінні, дочірній потік має виділити пам’ять для кожної локальної для потоку змінної, попередньо записаної у батьківському потоці. Це може призвести до значного збільшення обсягу пам’яті. Дочірні потоки не можуть спільно використовувати пам’ять, яку використовує батьківський потік, оскільки локальні змінні потоку можуть змінюватися, а
ThreadLocal
API вимагає, щоб мутація в одному потоці не була видимою для інших потоків. Це прикро, оскільки на практиці дочірні потоки рідко викликають методset(...)
для успадкованих ними локальних змінних потоку.
Проблеми локальних змінних потоку стали ще більш актуальними з появою віртуальних потоків (JEP 425). Віртуальні потоки — це легкі потоки, реалізовані у JDK. Багато віртуальних потоків використовують один і той самий потік операційної системи, що дозволяє створювати дуже велику кількість віртуальних потоків. Крім того, що віртуальних потоків багато, вони ще й досить дешеві, щоб представляти будь-яку паралельну одиницю поведінки. Це означає, що вебфреймворк може призначити новий віртуальний потік для обробки запиту і при цьому мати можливість обробляти тисячі або мільйони запитів одночасно.
Таким чином, локальні змінні потоку мають більшу складність, ніж зазвичай потрібно для обміну даними, і значні витрати, яких неможливо уникнути. Платформа Java повинна надавати можливість підтримувати незмінні та успадковані потокові дані для тисяч або мільйонів віртуальних потоків. Оскільки ці потокові змінні будуть незмінними, їхні дані можуть ефективно використовуватися дочірніми потоками. Крім того, час життя цих поточних змінних має бути обмеженим: будь-які дані, до яких надається спільний доступ через потокову змінну, повинні ставати непридатними для використання після завершення роботи методу, який спочатку надавав спільний доступ до цих даних.
JEP 432
JEP 432 або Record Pattern у другому Preview розширюють мову програмування Java шаблонами для деконструкції значень записів. Record Pattern і шаблони типів можна вкладати один в одного, щоб забезпечити декларативну, потужну і комбіновану форму навігації та обробки даних. Цілі включають розширення можливостей зіставлення шаблонів для вираження складніших, комбінованих запитів до даних, не змінюючи при цьому синтаксис або семантику шаблонів типів. Основні зміни, порівняно з першою попередньою версією JDK 19, включають додавання підтримки виведення типів аргументів типових шаблонів записів, підтримку шаблонів записів, що відображаються у заголовку розширеного оператора for
, та вилучення підтримки іменованих шаблонів записів. Отже, деконструкція типів дає змогу отримати доступ до складових record-классів через конструкцію patter matching:
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) { if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) { System.out.println("Upper-left corner: " + x); } }
А із JEP 432 впроваджується можливість використання паттернів усередині for-циклів:
record Point(int x, int y) {} static void dump(Point[] pointArray) { for (Point(var x, var y) : pointArray) { System.out.println("(" + x + ", " + y + ")"); } }
JEP 434
Foreign Function & Memory API (FFM) об’єднує два попередні інкубаційні API: Foreign Function Memory Access API(JEP 370, 383 і 393) і Foreign Linker API (JEP 389). API FFM було інкубовано в JDK 17 за допомогою JEP 412, повторно інкубовано в JDK 18 за допомогою JEP 419, і вперше попередньо переглянуто в JDK 19 за допомогою JEP 424. У цьому JEP пропонується включити доопрацювання, засновані на відгуках, і повторно переглянути API у JDK 20. Ключові зміни відносно JDK 19:
- уніфікація MemorySession & MemoryAddress для роботи із памʼяттю;
- клас MemorySession розділено на
Arena
таSegmentScope
, щоб полегшити спільне використання сегментів по всьому додатку.
Фактичною ж відмінністю є те, що Java наслідує концепцію арен памʼяті, яка була представлена у С++ як концептуальна ідея, в якій немає необхідності робити виділення памʼяті кожен раз за допомогою alloc/realloc/malloc. Концепція арени дозволяє одноразово виділити цілий сегмент памʼяті і позбутися його за одну операцію, що є суттєвим у порівнянні із виділенням памʼяті під змінні:
// 1. Find foreign function on the C library path Linker linker = Linker.nativeLinker(); SymbolLookup stdlib = linker.defaultLookup(); MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...); // 2. Allocate on-heap memory to store four strings String[] javaStrings = { "mouse", "cat", "dog", "car" }; // 3. Use try-with-resources to manage the lifetime of off-heap memory try (Arena offHeap = Arena.openConfined()) { // 4. Allocate a region of off-heap memory to store four pointers MemorySegment pointers = offHeap.allocateArray(ValueLayout.ADDRESS, javaStrings.length); // 5. Copy the strings from on-heap to off-heap for (int i = 0; i < javaStrings.length; i++) { MemorySegment cString = offHeap.allocateUtf8String(javaStrings[i]); pointers.setAtIndex(ValueLayout.ADDRESS, i, cString); } // 6. Sort the off-heap data by calling the foreign function radixsort.invoke(pointers, javaStrings.length, MemorySegment.NULL, '\0'); // 7. Copy the (reordered) strings from off-heap to on-heap for (int i = 0; i < javaStrings.length; i++) { MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i); javaStrings[i] = cString.getUtf8String(0); } } // 8. All off-heap memory is deallocated here assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"}); // true
Важливою зміною є впровадження SegmentScope
API. Задача цього API — вказання рантайму відносно якої «арени» відбувається виділення памʼяті — або глобальної, або локальної арени, створенної через try-with-resource. Різниця полягає у тому, що глобальна «арена» буде зачищена лише під час завершення застосунку.
JEP 436 & JEP 437
Назначні зміни торкнулися віртуальних потоків, наразі йде стабілізація кодової бази та робота над новими API. Якщо цікаво дізнатися більше, то рекомендую почитати ось цей пост. Те ж саме стосується і StructuredConcurrency, детальний опис доступний ось тут.
JEP 433
Pattern matching for switch statements and expressions дозволяє стисло і безпечно виражати складні запити, орієнтовані на дані. Четверта Preview версія, яка вже була представлена у JDK 17, JDK 18 та JDK 19, дозволить продовжити еволюцію шаблонів записів, що сприятиме вдосконаленню на основі досвіду та зворотного зв’язку. Основні зміни у зіставленні шаблонів для перемикачів порівняно з третьою попередньою версією включають спрощену граматику для міток перемикачів і підтримку виведення типів аргументів для загальних шаблонів і шаблонів записів у операторах і виразах перемикачів.
Дистрибутив JDK 20
Офіційні джерела
Офіційний реліз Oracle OpenJDK доступний тут.
Офіційний реліз OracleJDK доступний тут.
Офіційні Java action для GitHub
Реліз вже доступний для GitHub Actions:
steps:
- name: 'Set up latest Oracle OpenJDK 19'
uses: oracle-actions/setup-java@v1
with:
website: jdk.java.net
release: 20
або
steps:
- name: 'Set up latest Oracle JDK 19'
uses: oracle-actions/setup-java@v1
with:
website: java.net
release: 20
А також ми зробили Oracle JDK доступним через екшн від GitHub:
steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: 'oracle' java-version: '20' - run: java -cp java HelloWorldApp
Офіційна документація
Ліцензування
Трохи висновків
Java продовжує залишатися мовою програмування № 1 для сучасних технологічних тенденцій. Як показує своєчасне впровадження вдосконалень у Java 20, завдяки постійному ретельному плануванню та залученню екосистеми, платформа Java є добре позиціонованою для сучасного розвитку і зростання в хмарі.
Новий реліз хоч і не є Long-Time-Support версією, але маленькими кроками наближає нас до кращого майбутнього із віртуальними потоками, нативним кодом та зручністю програмування.
Слідкуйте за новинами та оновленнями за посиланнями:
- Dev.java (спеціальний портал Oracle для поглиблення ваших знань з Java та участі у комʼюніті).
- Inside.java (суто технічний блог, який ведуть архітектори та інженери, що розроблюють Java).
- Inside.java (подксат про JDK, JVM, GC, core libs і так далі).
- Inside.java Newscasts (щотижневе YouTube-шоу суто про Java).
- Java on YouTube (все про Java та екосистему).
- OpenJDK mailing lists (місце, де можна дізнатися поточний стан речей у OpenJDK комʼюніті).
- Підписуйтесь на OpenJDK and Java on Twitter.
----
Ну і підписуйтесь на мене у Twitter. Там я роблю щотижневий #JavaTuesdayThread — про поглиблений функціонал JDK, про C/C++ у контексті Java та ще багато чого іншного.
20 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів