Java21新特性
JDK 21 于 2023 年 9 月 19 日 发布,这是一个非常重要的版本,里程碑式。
JDK21 是 LTS(长期支持版),至此为止,目前有 JDK8、JDK11、JDK17 和 JDK21 这四个长期支持版了。
官方文档:OpenJDK Java 21 文档
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
JDK 21 共有 15 个新特性:
-
JEP 430:字符串模板 (预览)
-
JEP 431:有序集合
-
JEP 439:分代 ZGC
-
JEP 440:Record 模式
-
JEP 441:switch 模式匹配
-
JEP 442:外部函数和内存 API (第三次预览)
-
JEP 443:未命名模式和变量 (预览)
-
JEP 444:虚拟线程(正式特性)
-
JEP 445:未命名类和 main 方法 (预览)
-
JEP 446:作用域值 (预览)
-
JEP 448:向量 API(第六次孵化)
-
JEP 449:弃用 Windows 32 位 x86 端口
-
JEP 451:准备禁止动态加载代理
-
JEP 452:密钥封装机制 API 安全库
-
JEP 453:结构化并发(预览)
JEP 430:字符串模板 (预览)
String Templates(字符串模板) 目前仍然是 JDK 21 中的一个预览功能。
String Templates 提供了一种更简洁、更直观的方式来动态构建字符串。通过使用占位符${}
,我们可以将变量的值直接嵌入到字符串中,而不需要手动处理。在运行时,Java 编译器会将这些占位符替换为实际的变量值。并且,表达式支持局部变量、静态/非静态字段甚至方法、计算结果等特性。
该特性处理字符串的新方法称为:Template Expressions,即:模版表达式。它是 Java 中的一种新型表达式,不仅可以执行字符串插值,还可以编程,从而帮助开发人员安全高效地组成字符串。此外,模板表达式并不局限于组成字符串——它们可以根据特定领域的规则将结构化文本转化为任何类型的对象。
该特性主要目的是为了提高在处理包含多个变量和复杂格式化要求的字符串时的可读性和编写效率。
它的设计目标是:
- 通过简单的方式表达混合变量的字符串,简化 Java 程序的编写。
- 提高混合文本和表达式的可读性,无论文本是在单行源代码中(如字符串字面量)还是跨越多行源代码(如文本块)。
- 通过支持对模板及其嵌入式表达式的值进行验证和转换,提高根据用户提供的值组成字符串并将其传递给其他系统(如构建数据库查询)的 Java 程序的安全性。
- 允许 Java 库定义字符串模板中使用的格式化语法(java.util.Formatter ),从而保持灵活性。
- 简化接受以非 Java 语言编写的字符串(如 SQL、XML 和 JSON)的 API 的使用。
- 支持创建由字面文本和嵌入式表达式计算得出的非字符串值,而无需通过中间字符串表示。
Java 在没有 String Templates 之前,我们通常使用字符串拼接或格式化方法来构建字符串:
//concatenation
message = "Greetings " + name + "!";
//String.format()
message = String.format("Greetings %s!", name); //concatenation
//MessageFormat
message = new MessageFormat("Greetings {0}!").format(name);
//StringBuilder
message = new StringBuilder().append("Greetings ").append(name).append("!").toString();
有String Templates 后
Java 目前支持三种模板处理器:
- STR:自动执行字符串插值,即将模板中的每个嵌入式表达式替换为其值(转换为字符串)。
- FMT:和 STR 类似,但是它还可以接受格式说明符,这些格式说明符出现在嵌入式表达式的左边,用来控制输出的样式
- RAW:不会像 STR 和 FMT 模板处理器那样自动处理字符串模板,而是返回一个
StringTemplate
对象,这个对象包含了模板中的文本和表达式的信息
除了 JDK 自带的三种模板处理器外,你还可以实现 StringTemplate.Processor
接口来创建自己的模板处理器。
STR 模板处理器
STR
是 Java 平台定义的一种模板处理器。它通过用表达式的值替换模板中的每个嵌入表达式来执行字符串插值。使用 STR 的模板表达式的求值结果是一个字符串。
STR
是一个公共静态 final 字段,会自动导入到每个 Java 源文件中。
我们先看一个简单的例子:
@Test
public void STRTest() {
String sk = "Java 新特性";
String str1 = STR."\{sk},就是牛";
System.out.println(str1);
}
// 结果.....
Java 新特性,就是牛
上面的 STR."\{sk},就是牛"
就是一个模板表达式,它主要包含了三个部分:
- 模版处理器:
STR
- 包含内嵌表达式(
\{blog}
)的模版 - 通过
.
把前面两部分组合起来,形式如同方法调用
当模版表达式运行的时候,模版处理器会将模版内容与内嵌表达式的值组合起来,生成结果。
这个例子只是 STR模版处理器一个很简单的功能,它可以做的事情有很多。
数学运算
比如上面的 x + y = ?
:
@Test
public void STRTest() {
int x = 1,y =2;
String str = STR."\{x} + \{y} = \{x + y}";
System.out.println(str);
}
这种写法是不是简单明了了很多?
调用方法
STR模版处理器还可以调用方法,比如:
String str = STR."今天是:\{ LocalDate.now()} ";
当然也可以调用我们自定义的方法:
@Test
public void STRTest() {
String str = STR."\{getSkStr()},就是牛";
System.out.println(str);
}
public String getSkStr() {
return "Java 新特性";
}
访问成员变量
STR模版处理器还可以访问成员变量,比如:
public record User(String name,Integer age) {
}
@Test
public void STRTest() {
User user = new User("大明哥",18);
String str = STR."\{user.name()}今年\{user.age()}";
System.out.println(str);
}
注意点
需要注意的是,字符串模板表达式中的嵌入表达式数量没有限制,它从左到右依次求值,就像方法调用表达式中的参数一样。例如:
@Test
public void STRTest() {
int i = 0;
String str = STR."\{i++},\{i++},\{i++},\{i++},\{i++}";
System.out.println(str);
}
// 结果......
0,1,2,3,4
同时,表达式中也可以嵌入表达式:
@Test
public void STRTest() {
String name = "大明哥";
String sk = "Java 新特性";
String str = STR."\{name}的\{STR."\{sk},就是牛..."}";
System.out.println(str);
}
// 结果......
大明哥的Java 新特性,就是牛...
但是这种嵌套的方式会比较复杂,容易搞混,一般不推荐。
FMT 模板处理器
FMT 是 Java 定义的另一种模板处理器。它除了与STR模版处理器一样提供插值能力之外,还提供了左侧的格式化处理。下面我们来看看他的功能。比如我们要整理模式匹配的 Switch 表达在 Java 版本中的迭代,也就是下面这个表格
Java 版本 | 更新类型 | JEP | 更新内容 |
---|---|---|---|
Java 17 | 第一次预览 | JEP 406 | 引入模式匹配的 Swith 表达式作为预览特性。 |
Java 18 | 第二次预览 | JEP 420 | 对其做了改进和细微调整 |
Java 19 | 第三次预览 | JEP 427 | 进一步优化模式匹配的 Swith 表达式 |
Java 20 | 第四次预览 | JEP 433 | |
Java 21 | 正式特性 | JEP 441 | 成为正式特性 |
如果使用 STR 模板处理器,代码如下:
@Test
public void STRTest() {
SwitchHistory[] switchHistories = new SwitchHistory[]{
new SwitchHistory("Java 17","第一次预览","JEP 406","引入模式匹配的 Swith 表达式作为预览特性。"),
new SwitchHistory("Java 18","第二次预览","JEP 420","对其做了改进和细微调整"),
new SwitchHistory("Java 19","第三次预览","JEP 427","进一步优化模式匹配的 Swith 表达式"),
new SwitchHistory("Java 20","第四次预览","JEP 433",""),
new SwitchHistory("Java 21","正式特性","JEP 441","成为正式特性"),
};
String history = STR."""
Java 版本 更新类型 JEP 更新内容
\{switchHistories[0].javaVersion()} \{switchHistories[0].updateType()} \{switchHistories[0].jep()} \{switchHistories[0].content()}
\{switchHistories[1].javaVersion()} \{switchHistories[1].updateType()} \{switchHistories[1].jep()} \{switchHistories[1].content()}
\{switchHistories[2].javaVersion()} \{switchHistories[2].updateType()} \{switchHistories[2].jep()} \{switchHistories[2].content()}
\{switchHistories[3].javaVersion()} \{switchHistories[3].updateType()} \{switchHistories[3].jep()} \{switchHistories[3].content()}
\{switchHistories[4].javaVersion()} \{switchHistories[4].updateType()} \{switchHistories[4].jep()} \{switchHistories[4].content()}
""";
System.out.println(history);
}
得到的效果是这样的:
Java 版本 更新类型 JEP 更新内容
Java 17 第一次预览 JEP 406 引入模式匹配的 Swith 表达式作为预览特性。
Java 18 第二次预览 JEP 420 对其做了改进和细微调整
Java 19 第三次预览 JEP 427 进一步优化模式匹配的 Swith 表达式
Java 20 第四次预览 JEP 433
Java 21 正式特性 JEP 441 成为正式特性
是不是很丑?完全对不齐,没法看。为了解决这个问题,就可以采用FMT模版处理器,在每一列左侧定义格式:
@Test
public void STRTest() {
SwitchHistory[] switchHistories = new SwitchHistory[]{
new SwitchHistory("Java 17","第一次预览","JEP 406","引入模式匹配的 Swith 表达式作为预览特性。"),
new SwitchHistory("Java 18","第二次预览","JEP 420","对其做了改进和细微调整"),
new SwitchHistory("Java 19","第三次预览","JEP 427","进一步优化模式匹配的 Swith 表达式"),
new SwitchHistory("Java 20","第四次预览","JEP 433",""),
new SwitchHistory("Java 21","正式特性","JEP 441","成为正式特性"),
};
String history = FMT."""
Java 版本 更新类型 JEP 更新内容
%-10s\{switchHistories[0].javaVersion()} %-9s\{switchHistories[0].updateType()} %-10s\{switchHistories[0].jep()} %-20s\{switchHistories[0].content()}
%-10s\{switchHistories[1].javaVersion()} %-9s\{switchHistories[1].updateType()} %-10s\{switchHistories[1].jep()} %-20s\{switchHistories[1].content()}
%-10s\{switchHistories[2].javaVersion()} %-9s\{switchHistories[2].updateType()} %-10s\{switchHistories[2].jep()} %-20s\{switchHistories[2].content()}
%-10s\{switchHistories[3].javaVersion()} %-9s\{switchHistories[3].updateType()} %-10s\{switchHistories[3].jep()} %-20s\{switchHistories[3].content()}
%-10s\{switchHistories[4].javaVersion()} %-9s\{switchHistories[4].updateType()} %-10s\{switchHistories[4].jep()} %-20s\{switchHistories[4].content()}
""";
System.out.println(history);
}
输出如下:
Java 版本 更新类型 JEP 更新内容
Java 17 第一次预览 JEP 406 引入模式匹配的 Swith 表达式作为预览特性。
Java 18 第二次预览 JEP 420 对其做了改进和细微调整
Java 19 第三次预览 JEP 427 进一步优化模式匹配的 Swith 表达式
Java 20 第四次预览 JEP 433
Java 21 正式特性 JEP 441 成为正式特性
JEP 431:有序集合
我们先看在 Java 21 之前,访问Java中集合的第一个和最后一个元素的方式:
集合 | 获取第一个元素 | 获取最后一个元素 |
---|---|---|
List | list.get(0) |
list.get(list.size() - 1) |
Deque | deque.getFirst() |
deque.getLast() |
SortedSet | sortedSet.first() |
sortedSet.last() |
三个集合提供了三类不同的使用方法,非常混乱。为了解决这种混乱,Java 21 引入有序集合,旨在解决访问Java中各种集合类型的第一个和最后一个元素需要非统一且麻烦处理场景。
JDK 21 引入了一种新的集合类型:Sequenced Collections(序列化集合,也叫有序集合),这是一种具有确定出现顺序(encounter order)的集合(无论我们遍历这样的集合多少次,元素的出现顺序始终是固定的)。序列化集合提供了处理集合的第一个和最后一个元素以及反向视图(与原始集合相反的顺序)的简单方法。
Sequenced Collections 包括以下三个接口:
SequencedCollection
SequencedSet
SequencedMap
SequencedCollection
SequencedCollection
接口继承了 Collection
接口, 提供了在集合两端访问、添加或删除元素以及获取集合的反向视图的方法。
interface SequencedCollection<E> extends Collection<E> {
// New Method
SequencedCollection<E> reversed();
// Promoted methods from Deque<E>
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
List
和 Deque
接口实现了SequencedCollection
接口。
这里以 ArrayList
为例,演示一下实际使用效果:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1); // List contains: [1]
arrayList.addFirst(0); // List contains: [0, 1]
arrayList.addLast(2); // List contains: [0, 1, 2]
Integer firstElement = arrayList.getFirst(); // 0
Integer lastElement = arrayList.getLast(); // 2
List<Integer> reversed = arrayList.reversed();
System.out.println(reversed); // Prints [2, 1, 0]
SequencedSet
SequencedSet
接口直接继承了 SequencedCollection
接口并重写了 reversed()
方法。
interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {
SequencedSet<E> reversed();
}
SortedSet
和 LinkedHashSet
实现了SequencedSet
接口。
这里以 LinkedHashSet
为例,演示一下实际使用效果:
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>(List.of(1, 2, 3));
Integer firstElement = linkedHashSet.getFirst(); // 1
Integer lastElement = linkedHashSet.getLast(); // 3
linkedHashSet.addFirst(0); //List contains: [0, 1, 2, 3]
linkedHashSet.addLast(4); //List contains: [0, 1, 2, 3, 4]
System.out.println(linkedHashSet.reversed()); //Prints [5, 3, 2, 1, 0]
SequencedMap
SequencedMap
接口继承了 Map
接口, 提供了在集合两端访问、添加或删除键值对、获取包含 key 的 SequencedSet
、包含 value 的 SequencedCollection
、包含 entry(键值对) 的 SequencedSet
以及获取集合的反向视图的方法。
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);
// Promoted Methods from NavigableMap<K, V>
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
SortedMap
和LinkedHashMap
实现了SequencedMap
接口。
这里以 LinkedHashMap
为例,演示一下实际使用效果:
LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
map.put(1, "One");
map.put(2, "Two");
map.put(3, "Three");
map.firstEntry(); //1=One
map.lastEntry(); //3=Three
System.out.println(map); //{1=One, 2=Two, 3=Three}
Map.Entry<Integer, String> first = map.pollFirstEntry(); //1=One
Map.Entry<Integer, String> last = map.pollLastEntry(); //3=Three
System.out.println(map); //{2=Two}
map.putFirst(1, "One"); //{1=One, 2=Two}
map.putLast(3, "Three"); //{1=One, 2=Two, 3=Three}
System.out.println(map); //{1=One, 2=Two, 3=Three}
System.out.println(map.reversed()); //{3=Three, 2=Two, 1=One}
JEP 439:分代 ZGC
JDK21 中对 ZGC 进行了功能扩展,增加了分代 GC 功能。不过,默认是关闭的,需要通过配置打开:
// 启用分代ZGC
java -XX:+UseZGC -XX:+ZGenerational ...
在未来的版本中,官方会把 ZGenerational 设为默认值,即默认打开 ZGC 的分代 GC。在更晚的版本中,非分代 ZGC 就被移除。
In a future release we intend to make Generational ZGC the default, at which point -XX:-ZGenerational will select non-generational ZGC. In an even later release we intend to remove non-generational ZGC, at which point the ZGenerational option will become obsolete.
在将来的版本中,我们打算将 Generational ZGC 作为默认选项,此时-XX:-ZGenerational 将选择非分代 ZGC。在更晚的版本中,我们打算移除非分代 ZGC,此时 ZGenerational 选项将变得过时。
分代 ZGC 可以显著减少垃圾回收过程中的停顿时间,并提高应用程序的响应性能。这对于大型 Java 应用程序和高并发场景下的性能优化非常有价值。
JEP 440:Record 模式
记录模式在 Java 19 进行了第一次预览, 由 JEP 405 提出。JDK 20 中是第二次预览,由 JEP 432 提出。最终,记录模式在 JDK21 顺利转正。
Java 20 新特性已经详细介绍过记录模式,这里就不重复了。
JEP 441:switch 模式匹配
增强 Java 中的 switch 表达式和语句,允许在 case 标签中使用模式。当模式匹配时,执行 case 标签对应的代码。
在下面的代码中,switch 表达式使用了类型模式来进行匹配。
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();
};
}
switch 模式匹配在Java20中已经是第四次预览,在Java21转正。前面页详细介绍过,可看:Java 20 新特性
JEP 442:外部函数和内存 API (第三次预览)
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 JEP 412 提出。Java 18 中进行了第二次孵化,由JEP 419 提出。Java 19 中是第一次预览,由 JEP 424 提出。JDK 20 中是第二次预览,由 JEP 434 提出。JDK 21 中是第三次预览,由 JEP 442 提出。
在 Java 19 新特性 中,有详细介绍到外部函数和内存 API,这里就不再做额外的介绍了。
JEP 443:未命名模式和变量 (预览)
未命名模式和变量使得我们可以使用下划线 _
表示未命名的变量以及模式匹配时不使用的组件,旨在提高代码的可读性和可维护性。
未命名变量的典型场景是 try-with-resources
语句、 catch
子句中的异常变量和for
循环。当变量不需要使用的时候就可以使用下划线 _
代替,这样清晰标识未被使用的变量。
try (var _ = ScopedContext.acquire()) {
// No use of acquired resource
}
try { ... }
catch (Exception _) { ... }
catch (Throwable _) { ... }
for (int i = 0, _ = runOnce(); i < arr.length; i++) {
...
}
未命名模式是一个无条件的模式,并不绑定任何值。未命名模式变量出现在类型模式中。
if (r instanceof ColoredPoint(_, Color c)) { ... c ... }
switch (b) {
case Box(RedBall _), Box(BlueBall _) -> processBox(b);
case Box(GreenBall _) -> stopProcessing();
case Box(_) -> pickAnotherBox();
}
JEP 444:虚拟线程(正式特性)
虚拟线程是一项重量级的更新,一定一定要重视!
虚拟线程在 Java 19 中进行了第一次预览,由JEP 425提出。JDK 20 中是第二次预览。最终,虚拟线程在 JDK21 顺利转正。
具体可看:Java19新特性-虚拟线程(第一次预览)
JEP 445:未命名类和 main 方法 (预览)
这个特性主要简化了 main
方法的的声明。对于 Java 初学者来说,这个 main
方法的声明引入了太多的 Java 语法概念,不利于初学者快速上手。
没有使用该特性之前定义一个 main
方法:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
使用该新特性之后定义一个 main
方法:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
进一步精简(未命名的类允许我们不定义类名):
void main() {
System.out.println("Hello, World!");
}
JEP 446:作用域值 (预览)
作用域值,Java 20 引入的,但是在 Java 21 还处于预览阶段,具体还是看:Java20新特性
JEP 448:向量 API(第六次孵化)
JEP 448(Vector API(第六次孵化器))结合了对前五轮孵化反馈的增强:在 JDK 20 中发布的 JEP 438(Vector API(第五次孵化器))、在 JDK 19 中发布的 JEP 426(Vector API (第四次孵化器))、在 JDK 18 中发布的 JEP 417(Vector API (第三次孵化器))、在 JDK 17 中发布的 JEP 414(Vector API (第二次孵化器))、在 JDK 16 中作为孵化器模块发布的 JEP 338(Vector API (孵化器))。此功能建议增强 Vector API,以便可以从外部函数和内存 API 定义的MemorySegment
中加载和存储 Vector。
JEP 442(外部函数和内存API(第三次预览))基于之前的反馈进行了改进,并提供第三次预览:在 JDK 20 中发布的 JEP 434(外部函数和内存API(第二次预览))、在 JDK 19 中发布的 JEP 424(外部函数和内存API(预览)),以及相关的孵化——在 JDK 18 中发布的 JEP 419(外部函数和内存API(第二孵化器))和在 JDK 17 中发布的 JEP 412(外部函数和内存API(孵化器))。这个特性为 Java 应用程序提供了一个 API,可以通过有效地调用外部函数和安全地访问不受 JVM 管理的外部内存与 Java 运行时之外的代码和数据进行互操作。来自 JEP 434 的更新包括:在Arena
接口中集中管理本地段的生命周期、增强的布局路径,使用新元素来解引用地址布局、移除VaList
类。
开发人员可能会有兴趣了解外部函数和内存 API 所带来的性能提升,这个 API 预计将成为 JDK 22 的最终特性。
Java19新特性里有介绍其API:Java19新特性
JEP 449:弃用 Windows 32 位 x86 端口
JEP 449旨在弃用 Windows 的 32 位 x86 支持,最终目标是在将来完全删除它。
这种弃用及其未来删除背后的原因主要是技术性的。
为任何系统提供软件总是需要决定您实际想要支持哪些平台。针对不再受支持的平台或版本是可能的,但通常意味着增加支持工作、向后移植、自行修复内容等。
以Windows平台为例,最后一个32位版本于2020年发布,官方支持于2025年10月结束。
如果您知道 64 位 Windows 如何处理 32 位应用程序,您可能想知道为什么不能通过 Windows集成的 WOW64 模拟层来运行 JVM ?嗯,通常可以以这种方式运行应用程序,但性能会急剧下降。
这就是 OpenJDK 团队决定继续弃用的原因,因为它只影响 Java 的未来版本。旧系统仍然可以使用删除之前的所有 Java 版本。
Java 21 中的一项直接更改会影响 JDK 的构建过程,因为默认情况下禁用配置构建的可能性。尝试运行bash ./configure会出现错误:
...
checking compilation type... native
configure: error: The Windows 32-bit x86 port is deprecated and may be removed in a future release. \
Use --enable-deprecated-ports=yes to suppress this error.
configure exiting with result code 1
由于该功能只是被弃用,而不是被删除,因此 OpenJDK 团队添加了新的配置选项(如错误所示),--enable-deprecated-ports=yes
以仍然允许配置。但是,会发出警告以强调弃用和未来可能的删除。
$ bash ./configure --enable-deprecated-ports=yes
...
checking compilation type... native
configure: WARNING: The Windows 32-bit x86 port is deprecated and may be removed in a future release.
...
Build performance summary:
* Cores to use: 32
* Memory limit: 96601 MB
The following warnings were produced. Repeated here for convenience:
WARNING: The Windows 32-bit x86 port is deprecated and may be removed in a future release.
补充
Java 21 充满了令人敬畏的新功能,虚拟线程 (JEP 444)的添加就是其中之一。它引入了轻量级(虚拟)线程,这可能会通过减少编写、维护和观察此类应用程序所需的工作量,从而显着改变我们处理 Java 中高吞吐量并发应用程序的方式。它们的开销比传统平台(内核)线程少得多。
然而,在 Windows 32 位 x86 上,由于技术限制,此功能必须回退到内核线程。底层平台的这种缺失功能通常是未来弃用和删除的有力指标。
尽管如此,您仍然可以编写和使用新的线程代码,但在实际操作中却缺少预期的好处。
JEP 451:准备禁止动态加载代理
在Java21中将代理动态加载到正在运行的JVM中时发出警告。
这些警告旨在让用户为将来的版本做好准备,该版本默认情况下不允许动态加载代理,以提高默认情况下的完整性。在启动时加载代理的可服务性工具不会导致在任何版本中发出警告。
1.什么是动态加载代理禁用准备?
动态加载代理禁用准备(Prepare to Disallow the Dynamic Loading of Agents)是一个Java增强提案,其目标是在JVM中禁止动态加载代理。代理是一种能够修改或监视应用程序行为的机制,它可以通过字节码注入来实现。
2.为什么需要动态加载代理禁用准备?
动态加载代理允许开发人员在运行时修改和监视Java应用程序的行为。虽然这对于调试和性能分析等方面非常有用,但也存在潜在的安全风险。恶意代码可能会利用动态加载代理的功能来执行恶意操作,例如窃取敏感信息、篡改数据等。
因此,为了加强Java应用程序的安全性,限制动态加载代理的使用是很有必要的。
3.动态加载代理禁用准备的实现原理
动态加载代理禁用准备的实现涉及到以下几个方面:
3.1 修改ClassLoader
该提案建议修改Java虚拟机的类加载器,以阻止动态加载代理。具体而言,将在java.lang.ClassLoader
类中添加一个新的方法boolean disallowDynamicAgentLoading()
,默认返回false
。当该方法被调用时,将返回true
,表示禁止动态加载代理。
3.2 修改Instrumentation API
为了支持ClassLoader的修改,还需要对Java虚拟机的Instrumentation API进行相应的更改。具体而言,将在java.lang.instrument.Instrumentation
接口中添加一个新的方法boolean isDynamicAgentLoadingAllowed()
,默认返回true
。当该方法返回false
时,表示禁止动态加载代理。
3.3 更新安全管理器
此外,还建议更新Java虚拟机的安全管理器(SecurityManager),以允许检查是否允许动态加载代理。这样可以通过安全策略来控制哪些代码可以使用动态加载代理功能。
4.动态加载代理禁用准备的优点
- 提高Java应用程序的安全性:禁止动态加载代理可以防止恶意代码利用其功能执行潜在的危险操作。
- 简化安全配置:通过更新安全管理器和类加载器,可以更方便地控制动态加载代理的使用权限,简化安全配置过程。
5.动态加载代理禁用准备的缺点
- 可能影响现有代码:如果现有代码依赖于动态加载代理的功能,那么禁用它可能会导致这些代码无法正常工作。因此,在应用该增强提案之前,需要仔细评估现有代码的依赖关系。
6.动态加载代理禁用准备的使用示例
以下是一个简单的示例,展示了如何使用动态加载代理禁用准备:
import java.lang.instrument.Instrumentation;
public class AgentLoader {
public static void premain(String agentArgs, Instrumentation inst) {
// 禁止动态加载代理
if (inst.isDynamicAgentLoadingAllowed()) {
throw new SecurityException("Dynamic loading of agents is not allowed");
}
// 其他初始化操作...
}
}
在上述示例中,premain
方法是Java代理的入口点。通过调用isDynamicAgentLoadingAllowed()
方法,可以检查是否允许动态加载代理。如果不允许,则抛出安全异常。
7.动态加载代理禁用准备的使用注意事项
- 在使用动态加载代理禁用准备之前,需要仔细评估现有代码是否依赖于动态加载代理的功能。
- 需要更新相关的类加载器和安全管理器来支持禁止动态加载代理的功能。
- 使用动态加载代理禁用准备时,需要确保应用程序的安全策略能够正确地控制动态加载代理的使用权限。
JEP 452:密钥封装机制 API 安全库
Java21新特性 – 密钥封装机制 API 介绍一种用于密钥封装机制(Key Encapsulation Mechanism,简称KEM)的API,这是一种通过公共加密来保护对称密钥的加密技术。
该提案的一个目标是使应用程序能够使用KEM算法,如RSA密钥封装机制(RSA-KEM)、椭圆曲线集成加密方案(ECIES),以及美国国家标准与技术研究所(NIST)后量子密码学标准化过程的候选算法。
另一个目标是使KEM能够在更高级别的协议(如传输层安全性(TLS))和加密方案(如混合公钥加密(HPKE))中使用。
此外,安全提供商将能够在Java代码或本机代码中实现KEM算法,并包括在RFC 9180中定义的Diffie-Hellman KEM(DHKEM)的实现。
值得注意的是,最初针对 JDK 21 的JEP 404, Generational Shenandoah(实验性)已从 JDK 21 的最终功能集中正式删除。这是由于“审查过程中发现的风险以及缺乏时间”可以执行如此大的代码贡献所需的彻底审查。” Shenandoah 团队决定“尽其所能提供最好的 Generational Shenandoah”,并将寻求以 JDK 22 为目标。
JEP 453:结构化并发(预览)
Java19第一次孵化:JEP 428,Java20里是第二次孵化:JEP 437,在Java21里进行预览,相对于Java20,在这里建议将结构化并发作为java.util.concurrent
包中的预览API。唯一显著的变化是 StructuredTaskScope
::
fork(...)
方法返回一个Subtask,而不是Future。文章来源:https://www.toymoban.com/news/detail-856524.html
其他的可看:Java20新特性文章来源地址https://www.toymoban.com/news/detail-856524.html
到了这里,关于Java 21 新特性(LTS版本)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!