编辑推荐: |
本文主要介绍了Java21特性相关内容 。希望对你的学习有帮助。
本文来自于微信公众号大淘宝技术,由火龙果软件Linda编辑、推荐。 |
|
当前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.");
nbsp; return "Hello, Mario";
});
Supplier<Integer> task2 = scope.fork(()
-& gt; {
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) {
nbsp; 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.java
java --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. Feature: Sequenced 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. Feature: Generational 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,这两块后面升级后会做个应用实践,欢迎已经有实践经验的或者是有意向一起探索的小伙伴来交流。
|