Java21特性解读

原创
01/06 21:22
阅读数 9.3K



当前JDK的版本已经到了23了,不过最近的LTS版本是21,刚好最近准备把直播侧serverless应用的JVM环境升级到java21(目前是11),在升级前对21的特性做一个简单的了解和熟悉,下面是个人熟悉过程中的笔记,大家可以按照每一节特性中的代码自己在本地run下,可以更快地做个了解。


JDK的版本其实最近几年开始,已经是6个月一个版本了,LTS版本大概差不多间隔4-6个版本(不定),每次升级,都会有比较多的迭代,但是主要还是集中在几个方面:1. 新特性的支持,其实主要还是面向编写和阅读的自然语言化,做的新特性的提供或者语法糖的封装,突出易懂易用;2. 内部核心实现的性能或者能力的提升,感知比较多的是gc,或者是内部的hotspot的能力等;3. bugfix,漏洞修复等。


LTS版本还是值得去了解,有条件的话也是比较推荐在生产环境去做使用的,因为不管是上述哪个方面带来的提升,对开发以及系统运维来说,都是属于易得的红利。


介绍


  JEP


JEP是Java Enhancement Proposal的简称,JEP的提出到生效也需要一个过程,分别是: 

1. Draft(草稿)阶段:需要明确提议的动机、描述、目标和非目标(nongoals)、风险等; 

2. Submission(提交)阶段:该阶段会给该提议生成一个唯一的标识,叫JAB number; 

3. Review(审核)阶段:OpenJDK社区中和该提议比较相关的stakeholder会参与review,主要是看下这个提议对于Java的发展是否是必要、合理;

4. Sponsorship(赞助)阶段:这里主要是要有个开发团队去承接这个JEP的开发、测试和集成工作,该阶段还不需要实际投入开发; 

5. Candidate Status(候选)阶段:进入到该阶段说明整个JEP完成立项,并且价值和作用得到了充分认同,然后是要等待在哪个版本去做试验接入; 

6. Targeted(锁定版本)阶段:这个阶段已经为该JEP指定了一个JDK的版本,那需要把JEP对应的内容进行开发、测试; 

7. Integrated(集成)阶段:将JEP对应的代码集成到具体版本的JDK源码中,刚集成到JDK源码中的特性一般都是preview状态,通常都需要至少1个版本迭代周期才能变成可被正常使用的状态,处于preview状态的JEP,只能被试验性使用,不能被应用到生产环境,因为功能或者说特性可能在下一个版本中就会发生变化; 

8. Released(发布)阶段:该特性可以面向整体java开发者做使用,我们平时能用上的特性都属于这个范畴。


  SDKman

推荐一款比较不错的本地的管理各类语言版本的工具,SDKman,安装也很简单,可以进行不同版本的jdk的下载以及本地的环境切换;简单安装如下,一些命令可以参见 https://sdkman.io/

curl -s "https://get.sdkman.io" | bash


特性解读

  1. Feature:Unnamed Classes and Instance Main Methods (Preview)


动机:这个特性java推出比较早了,当时没有觉得学习门槛高,因为其他当时比较流行的语言,门槛更高,而且更难读,但是随着一代代发展下来,新出来的语音在语法和表达上,更接近于NL,那一相比较,就显得java有点“年龄大,水土不服”。


//package main.java.org.example;
void main(String[] args) { System.out.println("Hello world!");}

TIPS:下文中特性后面括号带Preview的,都是前瞻的功能,需要使用的话得加上 --enable-preview:

javac --release 21 --enable-preview Main_TL.java
// 或在IDEA的VM参数中添加--enable-preview

  2. Feature: String Templates (Preview)


动机:更直观,简洁,方便维护,同时增加了值的验证和转换。

不少JEP都有Non-Goals,这个看看还是蛮有趣的,希望是什么,不希望成为什么,这个特性的Non-Goals可以体会下:

非目标方向 

- 不是面向原有的字符串操作(+)提供语法糖; 

- 不是为了弃用或移除传统上用于复杂或程序化字符串组合的 StringBuilder 和 StringBuffer 类。


public class StringTemplate {    static String anotherName = "Jiang";
public static void main(String[] args) { String name = "Mario"; String number = "987689";
System.out.println(STR."name = \{name}, and number = \{number}, and anotherName = \{getAnotherName()}"); }
public static String getAnotherName() { return anotherName; }}

  3. Feature: Unnamed Patterns and Variables (Preview) & Record Patterns


(这两个feature比较关联的,就一起概述了) 

动机:提升可读性,并且让程序员可以清楚知道哪些成员变量被使用,哪些成员变量未被使用,preview的功能就是可以使用下划线来代替不想使用到的record里面的成员。


package org.example;
import java.util.List;
/** * 例子中sealed是java15给出的新特性,用于限制类继承,可以看做是枚举的扩展,java17中完善和标准化<br> * record是java14引入的新特性,用于创建不可变类,java16的时候成为正式标准 */public class UnnamedPatternsAndVariables { public static void main(String[] args) { process(List.of(new SealedClassA("A"), new SealedClassB("B", 123), new SealedClassC("C", 321, "hidden hobby"))); }
public static void process(List<SealedInterface> list) { for (SealedInterface sealedInterface : list) { if (sealedInterface instanceof SealedClassA(String name)) { System.out.println("The name of SealedClassA is " + name); } else if (sealedInterface instanceof SealedClassB(String name, Integer age)) { System.out.println("The name of SealedClassB is " + name + " and age is " + age); } else if (sealedInterface instanceof SealedClassC(String name, Integer age, String _)) { System.out.println("The name of SealedClassC is " + name + " and age is " + age + " and hobby is hidden"); } } }
public sealed interface SealedInterface permits SealedClassA, SealedClassB, SealedClassC {
}
public record SealedClassA(String name) implements SealedInterface {
}
public record SealedClassB(String name, Integer age) implements SealedInterface {
}
public record SealedClassC(String name, Integer age, String hobby) implements SealedInterface {
}}

  4. Feature: Scoped Values (Preview)

特性目标: 

易用性:提供一种编程模型,以便在一个线程内和子线程之间共享数据,从而简化对数据流的推理。 

可理解性:使共享数据的生命周期能够从代码的语法结构中可见。 

稳健性:确保调用者共享的数据只能被合法的被调用者检索。 

性能:允许共享数据是不可变的,以便于被大量线程共享,并支持运行时优化。


可以重新绑定,面向之前通过上下文context传递的场景,以及使用ThreadLocal的场景;如果在中途使用异步线程进行额外操作处理,这里的值绑定会丢失,需要显示的在线程之间做传递。


package org.example;
import java.lang.ScopedValue;
public class ScopedValues { public static void main(String[] args) { ScopedValue.runWhere(EagleEye.TRACE_ID, "123456", () -> { RPCProcess rpcProcess = new RPCProcess(); rpcProcess.process(); }); }}
class EagleEye { public static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();}
class RPCProcess { public void process() { System.out.println(STR."TRACE_ID: \{EagleEye.TRACE_ID.get()}, do rpc process"); ServerService serverService = new ServerService(); serverService.bizProcess(); }}
class ServerService { public void bizProcess() { System.out.println(STR."TRACE_ID: \{EagleEye.TRACE_ID.get()}, do biz process"); AsyncProcessor asyncProcessor = new AsyncProcessor(); asyncProcessor.asyncProc(); }}
class AsyncProcessor { public void asyncProc() { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(STR."TRACE_ID: \{!EagleEye.TRACE_ID.isBound() ? null : EagleEye.TRACE_ID.get()}, do async process"); } }); thread.start(); }}

  5. Feature: Foreign Function & Memory API (Third Preview)


动机:为了Java程序员提供更可靠的操作native代码和存储(操作系统层面)的API能力。


JDK19的时候首次提出,JDK20第二版preivew,JDK21第三版preview;该特性主要是为了提供一种更高效、便捷和安全的方式,去调用系统底层的类库,但是难点个人感觉还是在调用安全性上,这类工具在生产环境中的应用都需要比较慎重,因为都是属于运行时的错误,而且不容易测试发现。


package org.example;
import java.lang.foreign.Arena;import java.lang.foreign.MemorySegment;import java.lang.foreign.ValueLayout;
public class ForeignFunctionMemory { public static void main(String[] args) { MemorySegment memorySegment = Arena.ofAuto().allocate(1024);
memorySegment.set(ValueLayout.JAVA_BYTE, 0, (byte) 1); byte target = memorySegment.get(ValueLayout.JAVA_BYTE, 0);
System.out.println(target); }}

  6. Feature: Structured Concurrency (Preview)

动机:之前不同子的并发任务之间基本是独立,需要程序员自己去管理、组装以及显式调度,难免会出现一些线程泄漏(未正确关闭或回收线程)和取消延迟等问题;该特性是通过引入Scope的概念,简化java并发编程,把并发编程当成一个整体,把线程的管理和错误处理做了统一封装,让并发编程更傻瓜化,该特性在后续配合虚拟线程应该会有更广的应用。


package org.example;
import java.util.concurrent.StructuredTaskScope;import java.util.concurrent.TimeUnit;import java.util.function.Supplier;
public class StructuredConcurrency { public static void main(String[] args) {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier<String> task1 = scope.fork(() -> { System.out.println("task1 is running..."); TimeUnit.SECONDS.sleep(3); System.out.println("task1 is done."); return "Hello, Mario"; });
Supplier<Integer> task2 = scope.fork(() -> { System.out.println("task2 is running..."); TimeUnit.SECONDS.sleep(1); System.out.println("task2 is done."); return 666; });
scope.join().throwIfFailed(); System.out.println("all tasks are done.");
System.out.println("Task1 result:" + task1.get() + "\nTask2 result: " + task2.get()); } catch (Exception e) { throw new RuntimeException(e); } }}

new StructuredTaskScope.ShutdownOnFailure()默认使用的是虚拟线程,如果是要用普通的系统线程的话,可以调用另外一个构造方法,传入ThreadFactory即可。


  7. Feature: Vector API (Sixth Incubator)


引入一个API,用于表示向量计算,该API能够在运行时可靠地编译为支持的CPU架构上的最佳向量指令,从而实现比等效的标量计算更优越的性能。第六版了,从java16推出,然后17、18、19、20,到现在21都有做进一步的迭代孵化,现在依旧是个孵化阶段。


关注三个点:性能、可移植性(面向不同的CPU架构)、可靠性(即使在一些特殊的CPU架构下不能完美的利用硬件特性,但是也要保证在一定的效率下去正确的执行)。


package org.example;
import jdk.incubator.vector.IntVector;import jdk.incubator.vector.VectorSpecies;
public class VectorAPIDemo {
public static void main(String[] args) { int[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int[] array2 = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; int[] result = new int[array1.length];
VectorSpecies<Integer> vectorSpecies = IntVector.SPECIES_64; System.out.println("Vector Species Length: " + vectorSpecies.length());
for (int i = 0; i < array1.length; i += vectorSpecies.length()) { System.out.println("Vector Loop, i: " + i); IntVector vector1 = IntVector.fromArray(vectorSpecies, array1, i); IntVector vector2 = IntVector.fromArray(vectorSpecies, array2, i);
IntVector resultVector = vector1.add(vector2); resultVector.intoArray(result, i); System.out.printf("Result Vector: %s, and Result Array: %s%n", resultVector, toString(result)); }
for (int j : result) { System.out.println(j); } }
public static String toString(int[] result) { StringBuilder sb = new StringBuilder(); for (int j : result) { sb.append(j).append(","); } return sb.toString(); }}

javac --add-modules jdk.incubator.vector VectorAPI.javajava --add-modules jdk.incubator.vector VectorAPI.java

问题1:这里的向量内部数据类型都是封装类型,主要受限于java的泛型机制,目前有个叫Valhalla的项目在推进增强java泛型的能力; 

问题2:对于x64中的SIMD(是一种单指令多数据的并行处理技术)支持不太好,从运行性能上无法充分利用硬件特性; 

问题3:发现对于mac的M系列芯片也支持不太好,如果不是自身指定正确的Species的大小,都会出现bound越界的问题。


  8. Feature: Pattern Matching for switch


动机:增强Switch语法中对于数据类型的识别能力,提升Switch基于数据类型判断上的逻辑处理能力。


public static void main(String[] args) {        System.out.println("Hello world!");
System.out.println(formatterPatternSwitch(123)); // 输出: int 123 System.out.println(formatterPatternSwitch(456L)); // 输出: long 456 System.out.println(formatterPatternSwitch(789.0)); // 输出: double 789.000000 System.out.println(formatterPatternSwitch("Hello")); // 输出: String Hello System.out.println(formatterPatternSwitch(new Object())); // 输出: java.lang.Object@<hashcode> }
static String formatterPatternSwitch(Object obj) { return switch (obj) { case Integer i -> String.format("int %d", i); case Long l -> String.format("long %d", l); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> obj.toString(); }; }

  9. FeatureSequenced Collections

动机:对于有序的集合或者map类型的操作支持不够好,同时差异化比较大,所以新增加几个超类,来定义针对这类有序集合的操作,做了一个统一。


interface SequencedCollection<E> extends Collection<E> {    // new method    SequencedCollection<E> reversed();    // methods promoted from Deque    void addFirst(E);    void addLast(E);    E getFirst();    E getLast();    E removeFirst();    E removeLast();}

interface SequencedMap<K,V> extends Map<K,V> {    // new methods    SequencedMap<K,V> reversed();    SequencedSet<K> sequencedKeySet();    SequencedCollection<V> sequencedValues();    SequencedSet<Entry<K,V>> sequencedEntrySet();    V putFirst(K, V);    V putLast(K, V);    // methods promoted from NavigableMap    Entry<K, V> firstEntry();    Entry<K, V> lastEntry();    Entry<K, V> pollFirstEntry();    Entry<K, V> pollLastEntry();}

public class SequencedCollections {    public static void main(String[] args) {        LinkedHashSet<Integer> hashSet = new LinkedHashSet<>();
hashSet.add(10); hashSet.add(20); hashSet.add(30); hashSet.add(40); hashSet.add(50); hashSet.add(60); hashSet.add(70); hashSet.addFirst(0); hashSet.addLast(80);
System.out.println(STR."hashSet = \{hashSet} , first = \{hashSet.getFirst()} , last = \{hashSet.getLast()}, reversed= \{hashSet.reversed()}"); }}

  10. FeatureGenerational ZGC

动机:提升ZGC在面向不同JVM堆大小的性能和效率问题(之前是面向大堆有更好的性能收益,小堆可能未有明显收益或负向收益)。


ZGC是在java11点时候推出的,从java15开始基本上可以在生产环境下使用;ZGC的STW时间是微秒级,G1是毫秒到秒级别;ZGC 目前将所有对象存储在一起,而不考虑对象的年龄,因此每次运行时都必须收集所有对象,但是年轻对象相比年老对象(old objects)生命周期更短,因此其实对于年轻对象增加GC的频次可以在效率以及内存回收收益上都是比较可观的。


G1和ZGC都不属于传统意义上的分代垃圾回收器,但是G1里面还是通过监测和跟踪对象的存活周期做不同策略的回收,不过就目前看来这样的方式,都不如分代设计来的高效。


之前是non-generational的模式,就是面向一整块堆栈做操作,简单示意下:


这次的优化是增加一种叫generational的模式,非常类似之前的分代设计;

在21中要使用ZGC的话,在JVM启动参数里面要加入如下配置:

-XX:+UseZGC -XX:+ZGenerational

  11. Feature: Virtual Threads


虚拟线程是轻量的,对资源诉求非常小的实现,不需要为虚拟线程去构建线程池,比较好的做法是快速使用,快速释放,短平快,虚拟线程替代不了原有的平台线程,但是在一些非CPU密集也不是IO密集的操作上会更合适,比如像处理网络类的请求,或者是一些非常简单的并发的任务。


package org.example;
import java.util.Scanner;
public class VirtualThreads implements Runnable{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Use virtual threads? (true/false)"); boolean useVirtual = scanner.nextBoolean(); System.out.println("Use virtual threads: " + useVirtual);
long start = System.currentTimeMillis();
for (int i=0; i<100000; i++) { if (useVirtual) { Thread.startVirtualThread(new VirtualThreads()); } else { Thread thread = new Thread(new VirtualThreads()); thread.start(); } }
long end = System.currentTimeMillis(); System.out.println("Time: " + (end - start)); }
@Override public void run() { // empty }}

  12. Feature: Key Encapsulation Mechanism API

针对KEM提供相关的API和类库,KEM是一种用于安全密钥交换的技术,目标是保障交互的双方的密钥约定生成和传输的安全性。


package org.example;
import javax.crypto.KEM;import java.security.*;import java.util.Arrays;import java.util.Base64;
public class KEMDemo { public static void main(String[] args) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("X25519"); KeyPair keyPair = keyPairGenerator.generateKeyPair();
SecureRandom random = new SecureRandom(); KEM kemS = KEM.getInstance("DHKEM"); KEM.Encapsulated encapsulated = kemS.newEncapsulator(keyPair.getPublic(), random).encapsulate(); byte[] encapsulatedKey = encapsulated.key().getEncoded(); byte[] encapsulatedSymmetricKey = encapsulated.encapsulation(); byte[] decryptedSymmetricKey = kemS.newDecapsulator(keyPair.getPrivate()).decapsulate(encapsulatedSymmetricKey).getEncoded();
System.out.println("encapsulatedKey: " + Base64.getEncoder().encodeToString(encapsulatedKey)); System.out.println("encapsulatedSymmetricKey: " + Base64.getEncoder().encodeToString(encapsulatedSymmetricKey)); System.out.println("decryptedSymmetricKey: " + Base64.getEncoder().encodeToString(decryptedSymmetricKey));
System.out.println("encapsulatedKey and decryptedSymmetricKey is equal ? " + Arrays.equals(encapsulatedKey, decryptedSymmetricKey)); }}

结语

整体从Feature内容看,个人体感STR在使用上,还是提升不少便捷性的,在试用的时候也非常乐意去使用,期待后续尽早变成release状态;21中给到比较大的提升,个人认为是虚拟线程和ZGC,这两块后面升级后会做个应用实践,欢迎已经有实践经验的或者是有意向一起探索的小伙伴来交流。


参考资料


  • https://openjdk.org/projects/jdk/21/

  • https://learning.oreilly.com/course/java-21/9781836649113/




¤  拓展阅读  ¤

3DXR技术 |  终端技术 |  音视频技术

服务端技术 | 技术质量 | 数据算法






本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
16 收藏
1
分享
返回顶部
顶部