Java 22~24 新特性
Java 22新特性(2024年3月)
正式特性
外部函数与内存 API
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
外部函数与内存 API(FFM API)提供了标准化、类型安全的方式来从 Java 直接调用本地代码,并在 Java 侧描述函数签名和内存布局。比起 JNl,FFM 一般不需要编写 JNI胶水代码,调用链更简洁、可维护性更好
让我们看看用 FFM API 是怎么做的
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class ModernNativeCall {
public static void main(String[] args) throws Throwable {
// 获取本地链接器和符号查找器
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
// 直接调用系统已有的C标准库函数,无需自己编写C代码
MethodHandle sqrt = linker.downcallHandle(
stdlib.find("sqrt").orElseThrow(() ->
new RuntimeException("sqrt函数未找到")),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
double result = (double) sqrt.invoke(16.0);
System.out.println("sqrt(16) = " + result);
// 如果要调用自定义库,也只需要一个Java文件
try (Arena arena = Arena.ofConfined()) {
SymbolLookup customLib = SymbolLookup.libraryLookup("mycalculator", arena);
MethodHandle add = linker.downcallHandle(
customLib.find("add").orElseThrow(() ->
new RuntimeException("add函数未找到")),
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.JAVA_INT,
ValueLayout.JAVA_INT)
);
int sum = (int) add.invoke(10, 20);
System.out.println("10 + 20 = " + sum);
} // 库资源在这里自动释放
}
}这里有几个关键概念需要理解
- Linker 负责连接 Java 代码和本地代码
- SymbolLookup 用来查找本地库中的函数
- MethodHandle 是 Java 中已有的概念,表示本地函数的调用
要特别注意的是 Arena.ofconfined(),这是 FFM AP 中一个非常重要的内存管理概念。Arena 可以理解为一个内存管理区域,它控制着内存的生命周期。当你使用 try-with-resources 语句时,Arena 会在代码块结束时自动释放所有相关的内存资源,这样就避免了内存泄漏的问题。
FFM API还有一个强大的特性是 支持双向调用。不仅可以从 Java 调用C函数,还可以把 Java 方法作为函数指针传递给 C函数。比如你可以用 Java 写一个比较器,然后传给 C标准库的 qsort 函数
public class QsortExample {
// Java比较器方法
static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
return Integer.compare(
elem1.get(ValueLayout.JAVA_INT, 0),
elem2.get(ValueLayout.JAVA_INT, 0)
);
}
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
// 找到qsort函数
MethodHandle qsort = linker.downcallHandle(
linker.defaultLookup().find("qsort").orElseThrow(),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
ValueLayout.JAVA_LONG,
ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS)
);
// 把Java方法包装成函数指针
MethodHandle comparHandle = MethodHandles.lookup()
.findStatic(QsortExample.class, "qsortCompare",
MethodType.methodType(int.class,
MemorySegment.class,
MemorySegment.class));
MemorySegment comparFunc = linker.upcallStub(
comparHandle,
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT)),
Arena.ofAuto()
);
// 使用qsort排序数组
try (Arena arena = Arena.ofConfined()) {
MemorySegment array = arena.allocateFrom(
ValueLayout.JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
qsort.invoke(array, 10L, ValueLayout.JAVA_INT.byteSize(), comparFunc);
int[] sorted = array.toArray(ValueLayout.JAVA_INT);
System.out.println(java.util.Arrays.toString(sorted));
// 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
}FFM API现在支持几乎所有主流平台,性能相比 JN!可能有一定提升,特别是在频繁调用本地函数的场景下。
内存管理示例
import java.lang.foreign.*;
public class MemoryManagementExample {
public static void main(String[] args) {
// 使用 Arena 管理内存
try (Arena arena = Arena.ofConfined()) {
// 分配本地内存
MemorySegment segment = arena.allocate(1024);
// 写入数据
segment.setAtIndex(ValueLayout.JAVA_INT, 0, 42);
segment.setAtIndex(ValueLayout.JAVA_INT, 1, 24);
// 读取数据
int value1 = segment.getAtIndex(ValueLayout.JAVA_INT, 0);
int value2 = segment.getAtIndex(ValueLayout.JAVA_INT, 1);
System.out.println("值1: " + value1 + ", 值2: " + value2);
// 分配字符串
MemorySegment cString = arena.allocateFrom("Hello FFM API!");
System.out.println("C字符串长度: " + cString.byteSize());
} // Arena 自动释放所有内存
}
}未命名模式和变量
未命名模式和变量使得我们可以使用下划线 _ 表示未命名的变量以及模式匹配时不使用的组件,旨在提高代码的可读性和可维护性。
未命名变量的典型场景是 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();
}以下场景可以使用Unnamed Variables
- 局部变量
- try-with-resource
- 循环头中声明的变量
- catch中声明的变量
- lambda表达式中的参数
G1 的区域固定
Java 22 改进了 G1 垃圾收集器,引入了区域固定功能
# 启用 G1 垃圾收集器
java -XX:+UseG1GC MyApp
# G1 现在可以固定某些区域,避免在 GC 期间移动
# 这对于与本地代码交互的应用特别有用这个改进主要影响
- 本地内存交互:减少 GC 对本地代码的影响.。大对象处理:改善大对象的 GC 性能
- 延迟优化:减少某些 GC 暂停
启动多文件源代码程序
Java 22 扩展了单文件源代码程序的功能,现在支持多文件
# 可以直接运行多文件的 Java 程序
java Main.java
# 或者指定主类
java --main-class com.example.Main *.java这个特性让原型开发和脚本编写更加方便
// Main.java
import com.example.Utils;
public class Main {
public static void main(String[] args) {
Utils.printMessage("Hello Multi-file!");
}
}
// com/example/Utils.java
package com.example;
public class Utils {
public static void printMessage(String message) {
System.out.println("消息: " + message);
}
}预览特性
灵活的构造函数体(预览)
其实就是在super()上面可以添加代码了,目前还是预览状态
以前java语法规定了倘若使用super调用父类构造方法时,其必须出现在第一行,这有时就不利于我们编写更健壮的程序,比如:下面代码编译报错:
public class Student extends Person{
public Student(int age){
if(age < 0)
throw new IllegalArgumentException("年龄不能是负数");
super(age);//该语句必须出现在构造方法的第一行
}
}为了解决上面的问题,在java22中提出了pre-construction context,它允许我们在super()上面编写不访问对象的代码。
也就是:
- 允许super(...)和this(...) 之前添加别的语句
- 限制仍然存在
- 构造器自顶向下
- 字段在完成初始化之前不能被访问
- 依靠编译器的检查:根据编译错误来修改
示例代码1
public class Student extends Person{
public Student(int age){
this.toString();//报错
System.out.print(this);//报错
super.age;//报错
super(age);//该语句必须出现在构造方法的第一行
}
}示例代码2
public class User{
private int id;
public User(){
id++;
hashcode();
super();
}
}字符串模板(第二次预览)
String Templates(字符串模板) 在 JDK21 中第一次预览,JDK22 是第二次预览。在JDK23中被撤回了
String Templates 提供了一种更简洁、更直观的方式来动态构建字符串。通过使用占位符${},我们可以将变量的值直接嵌入到字符串中,而不需要手动处理。在运行时,Java 编译器会将这些占位符替换为实际的变量值。并且,表达式支持局部变量、静态/非静态字段甚至方法、计算结果等特性。
实际上,String Templates(字符串模板)再大多数编程语言中都存在:
"Greetings {{ name }}!"; //Angular
`Greetings ${ name }!`; //Typescript
$"Greetings { name }!" //Visual basic
f"Greetings { name }!" //PythonJava 在没有 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();这些方法或多或少都存在一些缺点,比如难以阅读、冗长、复杂。
Java 使用 String Templates 进行字符串拼接,可以直接在字符串中嵌入表达式,而无需进行额外的处理:
String message = STR."Greetings \{name}!";在上面的模板表达式中:
- STR 是模板处理器。
- \{name}为表达式,运行时,这些表达式将被相应的变量值替换。
Java 目前支持三种模板处理器:
- STR:自动执行字符串插值,即将模板中的每个嵌入式表达式替换为其值(转换为字符串)。
- FMT:和 STR 类似,但是它还可以接受格式说明符,这些格式说明符出现在嵌入式表达式的左边,用来控制输出的样式
- RAW:不会像 STR 和 FMT 模板处理器那样自动处理字符串模板,而是返回一个 StringTemplate 对象,这个对象包含了模板中的文本和表达式的信息
String name = "Lokesh";
//STR
String message = STR."Greetings \{name}.";
//FMT
String message = STR."Greetings %-12s\{name}.";
//RAW
StringTemplate st = RAW."Greetings \{name}.";
String message = STR.process(st);除了 JDK 自带的三种模板处理器外,你还可以实现 StringTemplate.Processor 接口来创建自己的模板处理器。
我们可以使用局部变量、静态/非静态字段甚至方法作为嵌入表达式:
//variable
message = STR."Greetings \{name}!";
//method
message = STR."Greetings \{getName()}!";
//field
message = STR."Greetings \{this.name}!";还可以在表达式中执行计算并打印结果:
int x = 10, y = 20;
String s = STR."\{x} + \{y} = \{x + y}"; //"10 + 20 = 30"为了提高可读性,可以将嵌入的表达式分成多行:
String time = STR."The current time is \{
//sample comment - current time in HH:mm:ss
DateTimeFormatter
.ofPattern("HH:mm:ss")
.format(LocalTime.now())
}.";类文件 API(预览)
Java 22 引入了类文件 API 作为预览特性,提供了读取、分析和生成 Java 类文件的标准方法
import java.lang.classfile.*;
import java.lang.constant.ClassDesc;
// 需要启用预览功能
public class ClassFileExample {
public static void main(String[] args) throws Exception {
// 读取现有的类文件
ClassModel classModel = ClassFile.of().parse(
ClassFileExample.class.getResourceAsStream("Example.class").readAllBytes()
);
// 分析类信息
System.out.println("类名: " + classModel.thisClass().asInternalName());
System.out.println("父类: " + classModel.superclass().orElse(null));
// 列出方法
classModel.methods().forEach(method -> {
System.out.println("方法: " + method.methodName().stringValue());
});
// 生成新的类文件
byte[] newClassBytes = ClassFile.of().build(
ClassDesc.of("com.example.Generated"),
classBuilder -> {
classBuilder.withMethod(
"hello",
MethodTypeDesc.of(ConstantDescs.CD_void),
ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
methodBuilder -> {
methodBuilder.withCode(codeBuilder -> {
codeBuilder.getstatic(
ClassDesc.of("java.lang.System"),
"out",
ClassDesc.of("java.io.PrintStream")
);
codeBuilder.ldc("Hello from generated class!");
codeBuilder.invokevirtual(
ClassDesc.of("java.io.PrintStream"),
"println",
MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String)
);
codeBuilder.return_();
});
}
);
}
);
// 保存生成的类文件
java.nio.file.Files.write(
java.nio.file.Paths.get("Generated.class"),
newClassBytes
);
}
}Stream Gatherers
Java 22 引入了 Stream Gatherers 作为预览特性
import java.util.stream.Gatherer;
import java.util.stream.Gatherers;
// 需要启用预览功能
public class GatherersExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用内置的 Gatherers
// 1. 滑动窗口
List<List<Integer>> windows = numbers.stream()
.gather(Gatherers.windowSliding(3))
.toList();
System.out.println("滑动窗口: " + windows);
// [[1, 2, 3], [2, 3, 4], [3, 4, 5], ...]
// 2. 固定窗口
List<List<Integer>> fixedWindows = numbers.stream()
.gather(Gatherers.windowFixed(3))
.toList();
System.out.println("固定窗口: " + fixedWindows);
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
// 3. 扫描(累积操作)
List<Integer> scan = numbers.stream()
.gather(Gatherers.scan(() -> 0, Integer::sum))
.toList();
System.out.println("扫描求和: " + scan);
// [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
// 自定义 Gatherer
Gatherer<String, ?, String> addPrefix = Gatherer.ofSequential(
() -> null, // 初始状态
(state, element, downstream) -> {
downstream.push("前缀-" + element);
return true;
}
);
List<String> words = List.of("apple", "banana", "cherry");
List<String> prefixed = words.stream()
.gather(addPrefix)
.toList();
System.out.println("添加前缀: " + prefixed);
// [前缀-apple, 前缀-banana, 前缀-cherry]
}
}结构化并发(第二次预览)
Java 22 继续完善结构化并发
import jdk.incubator.concurrent.StructuredTaskScope;
// 需要启用预览功能和孵化器模块
public class StructuredConcurrencyExample {
public static void main(String[] args) {
UserProfile profile = fetchUserProfile(123);
System.out.println(profile);
}
record UserProfile(User user, Settings settings, List<Notification> notifications) {}
record User(String name, String email) {}
record Settings(String theme, String language) {}
record Notification(String message, String type) {}
public static UserProfile fetchUserProfile(int userId) {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 并发执行多个任务
var userTask = scope.fork(() -> fetchUser(userId));
var settingsTask = scope.fork(() -> fetchSettings(userId));
var notificationsTask = scope.fork(() -> fetchNotifications(userId));
// 等待所有任务完成
scope.join();
scope.throwIfFailed();
// 组合结果
return new UserProfile(
userTask.resultNow(),
settingsTask.resultNow(),
notificationsTask.resultNow()
);
} catch (Exception e) {
throw new RuntimeException("获取用户资料失败", e);
}
}
private static User fetchUser(int userId) {
// 模拟数据库查询
simulateDelay(100);
return new User("用户" + userId, "user" + userId + "@example.com");
}
private static Settings fetchSettings(int userId) {
simulateDelay(150);
return new Settings("dark", "zh-CN");
}
private static List<Notification> fetchNotifications(int userId) {
simulateDelay(80);
return List.of(
new Notification("欢迎回来!", "info"),
new Notification("您有新消息", "alert")
);
}
private static void simulateDelay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}孵化器特性
向量 API(第七次孵化器)
Java 22 继续改进向量 API
import jdk.incubator.vector.*;
public class VectorAPIExample {
private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
public static void main(String[] args) {
// 向量化的数组操作
float[] a = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
float[] b = {8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f};
float[] result = new float[a.length];
// 向量化加法
vectorAdd(a, b, result);
System.out.println("向量加法: " + Arrays.toString(result));
// 向量化点积
float dotProduct = vectorDotProduct(a, b);
System.out.println("点积: " + dotProduct);
}
public static void vectorAdd(float[] a, float[] b, float[] result) {
int upperBound = SPECIES.loopBound(a.length);
// 向量化循环
for (int i = 0; i < upperBound; i += SPECIES.length()) {
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
FloatVector vr = va.add(vb);
vr.intoArray(result, i);
}
// 处理剩余元素
for (int i = upperBound; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
public static float vectorDotProduct(float[] a, float[] b) {
FloatVector sum = FloatVector.zero(SPECIES);
int upperBound = SPECIES.loopBound(a.length);
for (int i = 0; i < upperBound; i += SPECIES.length()) {
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
sum = va.fma(vb, sum);
}
float result = sum.reduceLanes(VectorOperators.ADD);
// 处理剩余元素
for (int i = upperBound; i < a.length; i++) {
result += a[i] * b[i];
}
return result;
}
}Java 23新特性(2024年9月)
正式特性
ZGC:默认的分代模式
ZGC垃圾回收器 (ZGC) 的默认模式切换为分代模式,并弃用非分代模式,计划在未来版本中移除。这是因为分代 ZGC 是大多数场景下的更优选择。
Markdown 文档注释
在 JavaDoc 文档注释中可以使用 Markdown 语法,取代原本只能使用 HTML 和 JavaDoc 标签的方式。
Markdown 更简洁易读,减少了手动编写 HTML 的繁琐,同时保留了对 HTML 元素和 JavaDoc 标签的支持。这个增强旨在让 API 文档注释的编写和阅读变得更加轻松,同时不会影响现有注释的解释。Markdown 提供了对常见文档元素(如段落、列表、链接等)的简化表达方式,提升了文档注释的可维护性和开发者体验。
/**
* # 用户服务类
*
* 这个类提供了用户管理的核心功能,包括:
*
* - **创建用户**:注册新用户账户
* - **查询用户**:根据ID或用户名查找
* - **更新用户**:修改用户信息
* - **删除用户**:注销用户账户
*
* ## 使用示例
*
* ```java
* UserService service = new UserService();
* User user = service.createUser("张三", "zhangsan@example.com");
* ```
*
* > **注意**:所有操作都需要适当的权限验证
*
* @author 鱼皮
* @version 1.0
* @since Java 23
*/
public class UserService {
/**
* ## 创建新用户
*
* 根据提供的信息创建一个新的用户账户。
*
* ### 参数说明
*
* | 参数 | 类型 | 描述 |
* |------|------|------|
* | name | String | 用户姓名,**不能为空** |
* | email | String | 电子邮箱,必须是有效格式 |
*
* ### 返回值
*
* 返回创建成功的 `User` 对象,包含:
* - 自动生成的用户ID
* - 创建时间戳
* - 默认的用户设置
*
* @param name 用户姓名
* @param email 电子邮箱
* @return 新创建的用户对象
* @throws IllegalArgumentException 当参数无效时抛出
*/
public User createUser(String name, String email) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("用户姓名不能为空");
}
if (email == null || !isValidEmail(email)) {
throw new IllegalArgumentException("无效的电子邮箱格式");
}
return new User(generateId(), name, email, Instant.now());
}
/**
* ### 验证邮箱格式
*
* 使用正则表达式验证邮箱格式是否有效:
*
* ```regex
* ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
* ```
*
* @param email 要验证的邮箱地址
* @return `true` 如果格式有效,否则 `false`
*/
private boolean isValidEmail(String email) {
return email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
}
private String generateId() {
return "USER_" + System.currentTimeMillis();
}
}
record User(String id, String name, String email, Instant createdAt) {}弃用 sun.misc.Unsafe 中的内存访问方法
JEP 471 提议弃用 sun.misc.Unsafe 中的内存访问方法,这些方法将来的版本中会被移除。
这些不安全的方法已有安全高效的替代方案:
java.lang.invoke.VarHandle:JDK 9 (JEP 193) 中引入,提供了一种安全有效地操作堆内存的方法,包括对象的字段、类的静态字段以及数组元素。java.lang.foreign.MemorySegment:JDK 22 (JEP 454) 中引入,提供了一种安全有效地访问堆外内存的方法,有时会与VarHandle协同工作。
这两个类是 Foreign Function & Memory API(外部函数和内存 API) 的核心组件,分别用于管理和操作堆外内存。Foreign Function & Memory API 在 JDK 22 中正式转正,成为标准特性。
import jdk.incubator.foreign.*;
import java.lang.invoke.VarHandle;
// 管理堆外整数数组的类
class OffHeapIntBuffer {
// 用于访问整数元素的VarHandle
private static final VarHandle ELEM_VH = ValueLayout.JAVA_INT.arrayElementVarHandle();
// 内存管理器
private final Arena arena;
// 堆外内存段
private final MemorySegment buffer;
// 构造函数,分配指定数量的整数空间
public OffHeapIntBuffer(long size) {
this.arena = Arena.ofShared();
this.buffer = arena.allocate(ValueLayout.JAVA_INT, size);
}
// 释放内存
public void deallocate() {
arena.close();
}
// 以volatile方式设置指定索引的值
public void setVolatile(long index, int value) {
ELEM_VH.setVolatile(buffer, 0L, index, value);
}
// 初始化指定范围的元素为0
public void initialize(long start, long n) {
buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
ValueLayout.JAVA_INT.byteSize() * n)
.fill((byte) 0);
}
// 将指定范围的元素复制到新数组
public int[] copyToNewArray(long start, int n) {
return buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
ValueLayout.JAVA_INT.byteSize() * n)
.toArray(ValueLayout.JAVA_INT);
}
}javac 中默认禁用注解处理
从 JDK23 开始,只有在显式配置注解处理或在 javac 命令行上显式请求运行注解处理时,才会运行注解处理。这和当前的默认行为不同,现有默认行为是在没有显式注解处理相关选项的情况下,通过搜索类路径中的处理器来运行注解处理。
预览特性
模式中的原始类型、instanceof 和 switch 中支持原始类型(预览)
在 JEP 455 之前, instanceof 只支持引用类型,switch 表达式和语句的 case 标签只能使用整数字面量、枚举常量和字符串字面量。
JEP 455 的预览特性中,instanceof 和 switch 全面支持所有原始类型,包括 byte, short, char, int, long, float, double, boolean。
// 传统写法
if (i >= -128 && i <= 127) {
byte b = (byte)i;
... b ...
}
// 使用 instanceof 改进
if (i instanceof byte b) {
... b ...
}
long v = ...;
// 传统写法
if (v == 1L) {
// ...
} else if (v == 2L) {
// ...
} else if (v == 10_000_000_000L) {
// ...
}
// 使用 long 类型的 case 标签
switch (v) {
case 1L:
// ...
break;
case 2L:
// ...
break;
case 10_000_000_000L:
// ...
break;
default:
// ...
}类文件 API(第二次预览)
类文件 API 在 JDK 22 进行了第一次预览,由 JEP 457 提出。
类文件 API 的目标是提供一套标准化的 API,用于解析、生成和转换 Java 类文件,取代过去对第三方库(如 ASM)在类文件处理上的依赖。
// 创建一个 ClassFile 对象,这是操作类文件的入口。
ClassFile cf = ClassFile.of();
// 解析字节数组为 ClassModel
ClassModel classModel = cf.parse(bytes);
// 构建新的类文件,移除以 "debug" 开头的所有方法
byte[] newBytes = cf.build(classModel.thisClass().asSymbol(),
classBuilder -> {
// 遍历所有类元素
for (ClassElement ce : classModel) {
// 判断是否为方法 且 方法名以 "debug" 开头
if (!(ce instanceof MethodModel mm
&& mm.methodName().stringValue().startsWith("debug"))) {
// 添加到新的类文件中
classBuilder.with(ce);
}
}
});Stream Gatherers(第二次预览)
流收集器在 JDK 22 进行了第一次预览,由 JEP 461 提出。
预览版功能:可以自定义中间操作了
source.gather(a).gather(b).gather(c).collect(...)原版stream流存在的问题
- 只能使用Stream类提供的方法,不能随意添加
- distinct 只能根据对象相等性来去重,无法根据对象的属性值来去重
模块导入声明 (预览)
模块导入声明允许在 Java 代码中简洁地导入整个模块的所有导出包,而无需逐个声明包的导入。这一特性简化了模块化库的重用,特别是在使用多个模块时,避免了大量的包导入声明,使得开发者可以更方便地访问第三方库和 Java 基本类。
此特性对初学者和原型开发尤为有用,因为它无需开发者将自己的代码模块化,同时保留了对传统导入方式的兼容性,提升了开发效率和代码可读性。
// 导入整个 java.base 模块,开发者可以直接访问 List、Map、Stream 等类,而无需每次手动导入相关包
import module java.base;
public class Example {
public static void main(String[] args) {
String[] fruits = { "apple", "berry", "citrus" };
Map<String, String> fruitMap = Stream.of(fruits)
.collect(Collectors.toMap(
s -> s.toUpperCase().substring(0, 1),
Function.identity()));
System.out.println(fruitMap);
}
}但是如果同一个模块中出现了同名的类,那么就必须恢复到之前的写法,显式导入了。
未命名类和实例 main 方法 (第三次预览)
Java21第一次预览,这个特性主要简化了 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!");
}这里连类都没有了,隐式声明类继承自 Object,不实现接口,并且不能在源代码中按名称引用。此外,实例主方法也不再强制要求它们是 static 或 public 的,并且不带参数的方法也可以作为有效的程序入口点。
结构化并发 (第三次预览)
Java 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代java.util.concurrent,目前处于孵化器阶段。
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
结构化并发的基本 API 是StructuredTaskScope。StructuredTaskScope 支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主任务继续之前完成。
StructuredTaskScope 的基本用法如下:
try (var scope = new StructuredTaskScope<Object>()) {
// 使用fork方法派生线程来执行子任务
Future<Integer> future1 = scope.fork(task1);
Future<String> future2 = scope.fork(task2);
// 等待线程完成
scope.join();
// 结果的处理可能包括处理或重新抛出异常
... process results/exceptions ...
} // close结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
作用域值 (第三次预览)
引入作用域值,它使得方法能够在线程内及其调用者之间共享不可变数据,以及与子线程共享。作用域值比线程局部变量更容易理解。当与虚拟线程(JEP 444)和结构化并发(JEP 480)一起使用时,它们还具有更低的空间和时间成本。
final static ScopedValue<...> V = new ScopedValue<>();
// In some method
ScopedValue.where(V, <value>)
.run(() -> { ... V.get() ... call methods ... });
// In a method called directly or indirectly from the lambda expression
... V.get() ...作用域值允许在大型程序中的组件之间安全有效地共享数据,而无需求助于方法参数。
灵活的构造函数体(第二次预览)
这个特性最初在 JDK 22 由 JEP 447: Statements before super(...) (Preview)提出。
Java 要求在构造函数中,super(...) 或 this(...) 调用必须作为第一条语句出现。这意味着我们无法在调用父类构造函数之前在子类构造函数中直接初始化字段。
灵活的构造函数体解决了这一问题,它允许在构造函数体内,在调用 super(..) 或 this(..) 之前编写语句,这些语句可以初始化字段,但不能引用正在构造的实例。这样可以防止在父类构造函数中调用子类方法时,子类的字段未被正确初始化,增强了类构造的可靠性。
这一特性解决了之前 Java 语法限制了构造函数代码组织的问题,让开发者能够更自由、更自然地表达构造函数的行为,例如在构造函数中直接进行参数验证、准备和共享,而无需依赖辅助方法或构造函数,提高了代码的可读性和可维护性。
class Person {
private final String name;
private int age;
public Person(String name, int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative.");
}
this.name = name;
this.age = age;
// ... 其他初始化代码
}
}
class Employee extends Person {
private final int employeeId;
public Employee(String name, int age, int employeeId) {
this.employeeId = employeeId; // 在调用父类构造函数之前初始化字段
super(name, age); // 调用父类构造函数
// ... 其他初始化代码
}
}字符串模板(撤回的JEP)
字符串模板首次在 JDK21 (JEP430) 中预览,并在 JDK22 (JEP 459) 中再次预览。经过反馈和广泛讨论,得出结论,该特性在当前形式下不适合。目前还没有更好的设计共识,因此暂时撤回了该特性,JDK23 将不包含它。
孵化器特性
向量 API(第八次孵化)
向量计算由对向量的一系列操作组成。向量 API 用来表达向量计算,该计算可以在运行时可靠地编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。
向量 API 的目标是为用户提供简洁易用且与平台无关的表达范围广泛的向量计算。
这是对数组元素的简单标量计算:
void scalarComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}这是使用 Vector API 进行的等效向量计算:
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
void vectorComputation(float[] a, float[] b, float[] c) {
int i = 0;
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length()) {
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(va)
.add(vb.mul(vb))
.neg();
vc.intoArray(c, i);
}
for (; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}Java 24新特性(2025年3月)
正式特性
类文件 API
类文件 API是一个专为框架和工具开发者设计的强大特性。长期以来,如果你想要在运行时动态生成、分析或修改 Java 字节码,就必须依赖像 ASM、Javassist 或者 CGLIB 这样的第三方库。
而且操作字节码需要深入了解底层细节,学习难度很大,我只能借助 AI 来搞定。
// 使用 ASM 库生成一个简单的类
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V17, ACC_PUBLIC, "com/example/GeneratedClass", null, "java/lang/Object", null);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 生成 sayHello 方法
mv = cw.visitMethod(ACC_PUBLIC, "sayHello", "()Ljava/lang/String;", null, null);
mv.visitCode();
mv.visitLdcInsn("Hello from generated class!");
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
cw.visitEnd();
byte[] bytecode = cw.toByteArray();有了类文件 API,操作字节码变得简单了一些
import java.lang.classfile.*;
import java.lang.classfile.constantpool.ConstantPoolBuilder;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
public byte[] generateClass() {
return ClassFile.of().build(ClassDesc.of("com.example.GeneratedClass"), cb -> {
// 添加默认构造函数
cb.withMethod("<init>", MethodTypeDesc.of(ConstantDescs.CD_void), ACC_PUBLIC, mb -> {
mb.withCode(codeb -> {
codeb.aload(0)
.invokespecial(ConstantDescs.CD_Object, "<init>", MethodTypeDesc.of(ConstantDescs.CD_void))
.return_();
});
});
// 添加 sayHello 方法
cb.withMethod("sayHello", MethodTypeDesc.of(ConstantDescs.CD_String), ACC_PUBLIC, mb -> {
mb.withCode(codeb -> {
codeb.ldc("Hello from generated class!")
.areturn();
});
});
});
}读取和分析现有的类文件也很简单
public void analyzeClass(byte[] classBytes) {
ClassModel cm = ClassFile.of().parse(classBytes);
System.out.println("类名: " + cm.thisClass().asInternalName());
System.out.println("方法列表:");
for (MethodModel method : cm.methods()) {
System.out.println(" - " + method.methodName().stringValue() +
method.methodType().stringValue());
}
}第三方字节码库可能需要一段时间才能跟上新特性的变化,而官方的类文件 API则能够与语言特性同步发布,确保开发者能够使用最新的字节码功能。
实际应用示例
import java.lang.classfile.*;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
public class DynamicClassGenerator {
public static void main(String[] args) throws Exception {
// 生成一个简单的数据类
byte[] personClassBytes = generatePersonClass();
// 将类文件写入磁盘
java.nio.file.Files.write(
java.nio.file.Paths.get("Person.class"),
personClassBytes
);
// 动态加载并使用生成的类
Class<?> personClass = loadClass("Person", personClassBytes);
Object person = personClass.getConstructor(String.class, int.class)
.newInstance("张三", 25);
System.out.println("生成的对象: " + person);
// 调用生成的方法
String name = (String) personClass.getMethod("getName").invoke(person);
int age = (int) personClass.getMethod("getAge").invoke(person);
System.out.println("姓名: " + name + ", 年龄: " + age);
}
// 生成 Person 类
public static byte[] generatePersonClass() {
return ClassFile.of().build(ClassDesc.of("Person"), classBuilder -> {
// 添加字段
classBuilder.withField("name", ConstantDescs.CD_String, ClassFile.ACC_PRIVATE | ClassFile.ACC_FINAL);
classBuilder.withField("age", ConstantDescs.CD_int, ClassFile.ACC_PRIVATE | ClassFile.ACC_FINAL);
// 构造函数
classBuilder.withMethod("<init>",
MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String, ConstantDescs.CD_int),
ClassFile.ACC_PUBLIC, methodBuilder -> {
methodBuilder.withCode(codeBuilder -> {
codeBuilder.aload(0) // this
.invokespecial(ConstantDescs.CD_Object, "<init>", MethodTypeDesc.of(ConstantDescs.CD_void))
.aload(0) // this
.aload(1) // name 参数
.putfield(ClassDesc.of("Person"), "name", ConstantDescs.CD_String)
.aload(0) // this
.iload(2) // age 参数
.putfield(ClassDesc.of("Person"), "age", ConstantDescs.CD_int)
.return_();
});
});
// getName 方法
classBuilder.withMethod("getName", MethodTypeDesc.of(ConstantDescs.CD_String),
ClassFile.ACC_PUBLIC, methodBuilder -> {
methodBuilder.withCode(codeBuilder -> {
codeBuilder.aload(0)
.getfield(ClassDesc.of("Person"), "name", ConstantDescs.CD_String)
.areturn();
});
});
// getAge 方法
classBuilder.withMethod("getAge", MethodTypeDesc.of(ConstantDescs.CD_int),
ClassFile.ACC_PUBLIC, methodBuilder -> {
methodBuilder.withCode(codeBuilder -> {
codeBuilder.aload(0)
.getfield(ClassDesc.of("Person"), "age", ConstantDescs.CD_int)
.ireturn();
});
});
// toString 方法
classBuilder.withMethod("toString", MethodTypeDesc.of(ConstantDescs.CD_String),
ClassFile.ACC_PUBLIC, methodBuilder -> {
methodBuilder.withCode(codeBuilder -> {
codeBuilder.ldc("Person{name='")
.aload(0)
.getfield(ClassDesc.of("Person"), "name", ConstantDescs.CD_String)
.invokevirtual(ConstantDescs.CD_String, "concat",
MethodTypeDesc.of(ConstantDescs.CD_String, ConstantDescs.CD_String))
.ldc("', age=")
.invokevirtual(ConstantDescs.CD_String, "concat",
MethodTypeDesc.of(ConstantDescs.CD_String, ConstantDescs.CD_String))
.aload(0)
.getfield(ClassDesc.of("Person"), "age", ConstantDescs.CD_int)
.invokestatic(ConstantDescs.CD_String, "valueOf",
MethodTypeDesc.of(ConstantDescs.CD_String, ConstantDescs.CD_int))
.invokevirtual(ConstantDescs.CD_String, "concat",
MethodTypeDesc.of(ConstantDescs.CD_String, ConstantDescs.CD_String))
.ldc("}")
.invokevirtual(ConstantDescs.CD_String, "concat",
MethodTypeDesc.of(ConstantDescs.CD_String, ConstantDescs.CD_String))
.areturn();
});
});
});
}
// 动态加载类
public static Class<?> loadClass(String name, byte[] classBytes) {
return new ClassLoader() {
@Override
protected Class<?> findClass(String name) {
return defineClass(name, classBytes, 0, classBytes.length);
}
}.loadClass(name);
}
}流收集器
Stream Gatherers API(JEP 461扩展)
流收集器 Stream::gather(Gatherer) 是一个强大的新特性,它允许开发者定义自定义的中间操作,从而实现更复杂、更灵活的数据转换。Gatherer 接口是该特性的核心,它定义了如何从流中收集元素,维护中间状态,并在处理过程中生成结果。例如,可以使用 Stream::gather 实现滑动窗口、自定义规则的去重、或者更复杂的状态转换和聚合。
复杂流处理示例:
List<Order> orders = ...;
List<Order> window = orders.stream().gather(Gatherers.windowSliding(5)) // 5元素滑动窗口
.filter(window -> window.stream().mapToDouble(Order::amount).average().orElse(0) > 1000)
.flatMap(List::stream)
.toList();新增内置Gatherers:
fold():实现可变状态聚合scan():生成中间结果的流fixedWindow():固定大小批处理
// 1. 滑动窗口 - windowSliding(size)
List<Double> prices = Arrays.asList(100.0, 102.0, 98.0, 105.0, 110.0);
List<Double> movingAverages = prices.stream()
.gather(Gatherers.windowSliding(3)) // 创建大小为 3 的滑动窗口
.map(window -> {
// window 是 List<Double> 类型,包含 3 个连续元素
return window.stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0);
})
.collect(Collectors.toList());
System.out.println("移动平均值: " + movingAverages);
// 移动平均值: [100.0, 101.66666666666667, 104.33333333333333]
// 2. 固定窗口 - windowFixed(size)
List<String> logs = Arrays.asList("log1", "log2", "log3", "log4", "log5");
List<List<String>> batches = logs.stream()
.gather(Gatherers.windowFixed(3)) // 每 3 个元素组成一个批次
.collect(Collectors.toList());
System.out.println("批量处理: " + batches);
// 批量处理: [[log1, log2, log3], [log4, log5]]性能特性:
- 比传统collect操作减少40%的中间集合分配
- 支持短路操作优化
除了内置的 Gatherers 外,还可以自定义 Gatherer,举一个最简单的例子 --给每个元素添加前缀。先自定义一个Gatherer
Gatherer<String, ?, String> addPrefix = Gatherer.ofSequential(
() -> null, // 不需要状态,所以初始化为 null
(state, element, downstream) -> {
// 给每个元素添加 "前缀-" 并推送到下游
downstream.push("前缀-" + element);
return true; // 继续处理下一个元素
}
// 不需要 finisher,省略第三个参数
);List<String> names = Arrays.asList("Seven", "面试", "八股");
List<String> prefixedNames = names.stream()
.gather(addPrefix)
.collect(Collectors.toList());
System.out.println(prefixedNames);
// 输出: [前缀-Seven, 前缀-面试, 前缀-八股]这个例子展示了 Gatherer 的最基本形态
- 不需要状态:第一个参数返回 nu,因为我们不需要维护任何状态
- 简单转换:第二个参数接收每个元素,做简单处理后推送到下游。
- 无需收尾:省略第三个参数,因为不需要最终处理虽然这个例子用 map()也能实现,但它帮助我们理解了 Gatherer 的基本工作机制。
这就是 Stream Gatherers 强大之处,它能够维护复杂的内部状态,并根据业务逻辑灵活地向下游推送结果,让原本需要手动循环的复杂逻辑变得简洁优雅。
Stream Gatherers 的另一个优势是它和现有的 Stream API完全兼容。你可以在 Stream 管道中的任何位置插入 Gatherer 操作,就像使用 map、filter 或 collect 一样自然,让复杂的数据处理变得既强大又优雅。
实际应用示例
import java.util.stream.Gatherer;
import java.util.stream.Gatherers;
public class StreamGatherersAdvanced {
public static void main(String[] args) {
// 股票价格分析
List<StockPrice> prices = List.of(
new StockPrice("AAPL", 150.0, "2024-01-01"),
new StockPrice("AAPL", 152.0, "2024-01-02"),
new StockPrice("AAPL", 148.0, "2024-01-03"),
new StockPrice("AAPL", 155.0, "2024-01-04"),
new StockPrice("AAPL", 158.0, "2024-01-05"),
new StockPrice("AAPL", 160.0, "2024-01-06")
);
// 使用滑动窗口计算移动平均
System.out.println("=== 5日移动平均 ===");
prices.stream()
.gather(Gatherers.windowSliding(3))
.forEach(window -> {
double avg = window.stream()
.mapToDouble(StockPrice::price)
.average()
.orElse(0.0);
String lastDate = window.get(window.size() - 1).date();
System.out.printf("%s: %.2f\n", lastDate, avg);
});
// 自定义 Gatherer - 检测价格突破
Gatherer<StockPrice, ?, PriceBreakthrough> breakthroughDetector =
Gatherer.ofSequential(
() -> new BreakthroughState(),
(state, price, downstream) -> {
if (state.previousPrice != null) {
double change = (price.price() - state.previousPrice) / state.previousPrice * 100;
if (Math.abs(change) > 3.0) { // 超过3%的变化
downstream.push(new PriceBreakthrough(
price.date(),
state.previousPrice,
price.price(),
change
));
}
}
state.previousPrice = price.price();
return true;
}
);
System.out.println("\n=== 价格突破检测 ===");
prices.stream()
.gather(breakthroughDetector)
.forEach(breakthrough -> {
System.out.printf("%s: %.2f -> %.2f (%.2f%%)\n",
breakthrough.date(),
breakthrough.fromPrice(),
breakthrough.toPrice(),
breakthrough.changePercent()
);
});
// 批量处理示例
List<String> tasks = List.of(
"task1", "task2", "task3", "task4", "task5",
"task6", "task7", "task8", "task9", "task10"
);
System.out.println("\n=== 批量处理任务 ===");
tasks.stream()
.gather(Gatherers.windowFixed(3))
.forEach(batch -> {
System.out.println("处理批次: " + batch);
// 模拟批量处理
batch.forEach(task -> {
try {
Thread.sleep(100); // 模拟处理时间
System.out.println(" 完成: " + task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
});
}
record StockPrice(String symbol, double price, String date) {}
record PriceBreakthrough(String date, double fromPrice, double toPrice, double changePercent) {}
static class BreakthroughState {
Double previousPrice = null;
}
}删除 Windows 32 位 x86 端囗
Java 24 正式移除了对 Windows 32 位系统的支持
提前类加载和链接
Java 24 引入了提前类加载和链接功能,可以在应用启动前预加载类
# 启用提前类加载
java -XX:+EagerClassLoading MyApp
# 配合类数据共享使用
java -XX:+EagerClassLoading -XX:SharedArchiveFile=myapp.jsa MyApp这个特性可以
- 减少启动时间:预加载常用类
- 提高缓存命中率:类信息提前准备
- 优化内存布局:更好的内存分配策略
永久禁用安全管理器
安全性管理器(Security Manager)并不是Java客户端代码的主要安全手段,也极少用于服务器端代码。此外,维护它成本高昂。因此,在Java 17中通过JEP 411: Deprecate the Security Manager for Removal将其弃用以备移除。本特性则完全禁止开发者启用安全性管理器,Security Manager API将在未来的版本中被移除。
ZGC 删除非分代模式
Java 24 完全移除了 ZGC 的非分代模式
G1垃圾回收器屏障优化提高效率
JEP 475: Late Barrier Expansion for G1
该特性主要是将Late Barrier Expansion引进到G1中。Barrier expansion是指在垃圾回收过程中插入或生成额外代码(称为“屏障”)以管理内存并确保垃圾回收的正确性。这些屏障通常被插入到字节码中的特定位置,例如在内存访问之前或之后,以执行以下任务:
- 记住写操作:跟踪对象的更改,这有助于垃圾回收器识别需要扫描的堆的部分。例如,写屏障(write barrier)会在每次存储操作之前执行,记录哪些对象被修改,从而帮助垃圾回收器维护对象的可达性信息。
- 保持一致性:确保程序对内存的视图与垃圾回收器的视图保持一致,特别是在并发垃圾回收阶段。例如,读屏障(read barrier)会在读取操作之前检查指针是否指向堆内存,并记录这些操作,以防止垃圾回收器误判对象的可达性。
- 处理引用:管理对象之间的引用,特别是在压缩或迁移阶段。例如,在垃圾回收的增量收集中,屏障可以阻止指向未分配空间(fromspace)的指针进入寄存器,从而避免垃圾回收器无法追踪到这些对象。
Early barrier expansion的含义是这些屏障在编译过程的早期插入或生成,而如果在过程的后期进行(正如JEP所提议的),则可以实现更优化的放置,并可能减少这些屏障相关的开销,具体为:
- 动态屏障插入:在JIT编译的优化阶段而非解析阶段插入写屏障,基于实际使用模式生成最小化屏障代码
- 条件屏障消除:通过逃逸分析识别不需要屏障的内存操作,在安全情况下完全省略屏障
- SIMD屏障优化:对数组批量操作生成向量化屏障指令,提升批量写操作的吞吐量 基准测试显示,该优化使G1在写密集型负载下的吞吐量提升12%,同时减少JIT编译代码大小5-7%。对于使用大量
ConcurrentHashMap或CopyOnWriteArrayList的并发应用收益尤为明显。
虚拟线程的同步而不固定平台线程
JEP 491: Synchronize Virtual Threads without Pinning 优化了虚拟线程与 synchronized 的工作机制。
JDK21引入虚拟线程时还有个pinning的问题,就是当虚拟线程在其载体上运行同步代码块时,它无法从载体上卸载。比如:
class CustomerCounter {
private final StoreRepository storeRepo;
private int customerCount;
CustomerCounter(StoreRepository storeRepo) {
this.storeRepo = storeRepo;
customerCount = 0;
}
synchronized void customerEnters() {
if (customerCount < storeRepo.fetchCapacity()) {
customerCount++;
}
}
synchronized void customerExits() {
customerCount--;
}
}如果是单纯调用storeRepo.fetchCapacity()则没问题,虚拟线程会从其载体unmount,释放平台线程给其他虚拟线程mount;但是如果是调用customerEnters,它用synchronized修饰则JVM会将该虚拟线程pin住防止其被unmount,这样子的话虚拟线程与平台线程都会blocked,直到fetchCapacity方法返回。
之所以pinning是因为synchronized依赖于monitors来确保它们只能由单个线程同时进入。在进入synchronized块之前,线程必须获取与实例相关联的monitor。JVM在平台线程级别跟踪这些monitor的所有权,而不是在虚拟线程级别跟踪。基于这些信息,假设不存在pinning,理论上,虚拟线程#1可以在synchronized块中间卸载,而虚拟线程#2可以装载到相同的平台线程上,并继续执行该synchronized块,因为承载线程是相同的,仍然持有对象的monitor。
从Java 24开始,虚拟线程可以获取、持有和释放监视器,而无需绑定到其载体线程。这意味着由于线程pinning而切换到不同的锁机制已不再是必需的。从现在起,无论是使用虚拟线程还是其他方法,性能表现都将相当一致。
在少数情况下,虚拟线程仍然会被pinning,其中一个情况是当它调用本地代码并返回到执行阻塞操作的Java代码时。在这种情况下,JDK Flight Recorder(JFR)会记录一个jdk.VirtualThreadPinned事件,如果要跟踪这些情况,可以启用JFR。
使用sun.misc.Unsafe内存访问方法时发出警告
JEP 498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe
JDK9的JEP 193: Variable Handles引入了VarHandle API用于替代sun.misc.Unsafe
JDK14的JEP 370: Foreign-Memory Access API (Incubator)引入了Foreign-Memory Access API作为incubator
JDK15的JEP 383: Foreign-Memory Access API (Second Incubator)Foreign-Memory Access API作为第二轮incubator
JDK16的JEP 393: Foreign-Memory Access API (Third Incubator)作为第三轮,它引入了Foreign Linker API (JEP 389)
FFM API在JDK 17的JEP 412: Foreign Function & Memory API (Incubator)作为incubator引入
FFM API在JDK 18的JEP 419: Foreign Function & Memory API (Second Incubator)作为第二轮incubator
JDK19的JEP 424: Foreign Function & Memory API (Preview)则将FFM API作为preview API
JDK20的JEP 434: Foreign Function & Memory API (Second Preview)作为第二轮preview
JDK21的JEP 442: Foreign Function & Memory API (Third Preview)作为第三轮preview
JDK22的JEP 454: Foreign Function & Memory API则正式发布此特性
JDK23的JEP 471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal废弃sun.misc.Unsafe,以便后续版本移除
JDK24默认情况下将在首次使用任何内存访问方法时发出警告,无论这些方法是直接调用还是通过反射调用。也就是说,无论使用了哪些内存访问方法,以及任何特定方法被调用的次数如何,最多只会发出一次警告。这将提醒应用程序开发者和用户即将移除这些方法,并需要升级库。
这些不安全的方法已有安全高效的替代方案:
java.lang.invoke.VarHandle:JDK 9 (JEP 193) 中引入,提供了一种安全有效地操作堆内存的方法,包括对象的字段、类的静态字段以及数组元素。java.lang.foreign.MemorySegment:JDK 22 (JEP 454) 中引入,提供了一种安全有效地访问堆外内存的方法,有时会与VarHandle协同工作。
这两个类是 Foreign Function & Memory API(外部函数和内存 API) 的核心组件,分别用于管理和操作堆外内存。Foreign Function & Memory API 在 JDK 22 中正式成为标准特性。
预览特性
新增密钥派生函数API支持现代加密标准
JEP 478: Key Derivation Functions API引入了符合NIST SP 800-56C标准的密钥派生实现。
随着量子计算领域的进步,传统加密算法变得更容易受到实际攻击。因此,Java平台必须整合后量子密码学(PQC),以抵御这些威胁。Java的长期目标是最终实现混合公钥加密(HPKE),以便无缝过渡到量子安全加密。JDK 21中包含的KEM API(JEP 452)是HPKE的一个组成部分,标志着Java朝着HPKE迈出的第一步,并为后量子挑战做好了准备。该JEP提出了HPKE的另一个组成部分,作为这一方向上的下一步:密钥派生函数(KDFs)的API。
使用示例如下:
// 示例:使用HKDF-SHA256从主密钥派生会话密钥
KeyDerivationFunction kdf = KeyDerivationFunctions.of("HKDF-SHA256");
SecretKey sessionKey = kdf.deriveKey(masterKey,
"SessionKey".getBytes(StandardCharsets.UTF_8),
256, // 密钥长度
new byte[32] // 可选盐值 );该API支持:
- HKDF:基于HMAC的提取-扩展密钥派生框架
- PBKDF2:密码-Based密钥派生,替代已废弃的
PBEKeySpec - Argon2:抗侧信道攻击的内存困难型算法 特别在微服务间TLS通信场景中,开发者现在可以标准化密钥派生流程,避免各服务实现不一致导致的安全隐患。
后量子加密技术前瞻性支持
JEP 496:Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism
引入了三种抗量子计算攻击的算法:
- ML-KEM(原CRYSTALS-Kyber):基于格理论的密钥封装机制
- ML-DSA(原CRYSTALS-Dilithium):数字签名算法
- SLH-DSA:基于哈希的签名方案
// 生成抗量子密钥对示例
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-DSA");
kpg.initialize(new MLDSAParameterSpec(MLDSAParameterSpec.ML_DSA_65));
KeyPair keyPair = kpg.generateKeyPair();``
虽然这些算法尚未进入最终标准,但预览版允许金融、政务等敏感领域提前进行技术验证和性能测试,为即将到来的量子计算时代做好准备。
Scoped Values(第四次预览)
JDK19的JEP 428: Structured Concurrency (Incubator)作为第一次incubator
JDK20的JEP 437: Structured Concurrency (Second Incubator)作为第二次incubator
JDK21的JEP 453: Structured Concurrency (Preview)作为首次preview
JDK22的JEP 462: Structured Concurrency (Second Preview)作为第二次preview
JDK23的JEP 480: Structured Concurrency (Third Preview)作为第三次preview
JDK24则作为第四次preview,与JDK23不同的是callWhere以及runWhere方法从ScopedValue类中移除,可以使用ScopedValue.where()再链式调用run(Runnable)或者call(Callable)
并发编程模型革新:
final static ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
void processRequest(Request req) {
ScopedValue.where(CURRENT_USER, fetchUser(req)).run(() -> handleRequest());
}
void handleRequest() {
User user = CURRENT_USER.get(); // 线程内安全访问
// ...业务逻辑
}技术对比:
| 特性 | ThreadLocal | ScopedValue |
|---|---|---|
| 内存泄漏风险 | 高 | 零 |
| 子线程继承 | 需显式传递 | 自动继承 |
| 性能开销 | 约15ns/访问 | 约3ns/访问 |
灵活构造函数体(第三次预览)
JEP 492 Flexible Constructor Bodies (Third Preview)
JDK22的JEP 447: Statements before super(...) (Preview)作为第一次preview
JDK23的JEP 482: Flexible Constructor Bodies (Second Preview)作为第二次preview
JDK24作为第三次preview
灵活的构造函数体允许在构造函数体内,在调用 super(..) 或 this(..) 之前编写语句,这些语句可以初始化字段,但不能引用正在构造的实例。这样可以防止在父类构造函数中调用子类方法时,子类的字段未被正确初始化,增强了类构造的可靠性。
这一特性解决了之前 Java 语法限制了构造函数代码组织的问题,让开发者能够更自由、更自然地表达构造函数的行为,例如在构造函数中直接进行参数验证、准备和共享,而无需依赖辅助方法或构造函数,提高了代码的可读性和可维护性。
class Person {
private final String name;
private int age;
public Person(String name, int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative.");
}
this.name = name;
this.age = age;
// ... 其他初始化代码
}
}
class Employee extends Person {
private final int employeeId;
public Employee(String name, int age, int employeeId) {
this.employeeId = employeeId; // 在调用父类构造函数之前初始化字段
super(name, age); // 调用父类构造函数
// ... 其他初始化代码
}
}原始类型模式匹配(第二次预览)
JEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
JDK19的JEP 405: Record Patterns (Preview)将Record的模式匹配作为第一次preview
JDK20的JEP 432: Record Patterns (Second Preview)作为Record模式匹配第二次preview
JDK21的JEP 440: Record Patterns则将Record模式匹配正式发布
JDK23的JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)将原始类型的匹配作为第一次preview
JDK24作为第二次preview
技术实现:
// 传统类型检查与转换
if (obj instanceof Integer) {
int value = ((Integer)obj).intValue();
System.out.println(value * 2);
}
// Java 24新模式
if (obj instanceof int value) {
System.out.println(value * 2);
// 自动拆箱为原始类型
}底层优化:
- 字节码层面消除冗余的类型转换指令
- 模式变量直接绑定到原始类型而非包装类
- JIT编译器可进行更激进的标量替换优化
性能影响:
- 数值计算密集型代码性能提升8-12%
- 减少50%的临时对象分配
结构化并发(第四次预览)
JEP 499: Structured Concurrency (Fourth Preview)
JDK19的JEP 428: Structured Concurrency (Incubator)作为第一次incubator
JDK20的JEP 437: Structured Concurrency (Second Incubator)作为第二次incubator
JDK21的JEP 453: Structured Concurrency (Preview)作为首次preview
JDK22的JEP 462: Structured Concurrency (Second Preview)作为第二次preview
JDK23的JEP 480: Structured Concurrency (Third Preview)作为第三次preview
JDK24作为第四次preview
JDK 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代java.util.concurrent,目前处于孵化器阶段。
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
错误处理改进:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> fetchUser());
Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // 等待所有子任务
return new Response(user.resultNow(), order.resultNow()); // 自动处理取消和异常传播
}结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
新增特性:
- deadline支持:
scope.withDeadline(Instant.now().plusSeconds(5)) - 嵌套scope的层次化取消
- 与虚拟线程深度集成
简化源文件启动(第四次预览)
JEP 495: Simple Source Files and Instance Main Methods (Fourth Preview)
没有使用该特性之前定义一个 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!");
}这里连类都没有了,隐式声明类继承自 Object,不实现接口,并且不能在源代码中按名称引用。此外,实例主方法也不再强制要求它们是 static 或 public 的,并且不带参数的方法也可以作为有效的程序入口点。
编译执行变化:
- 隐式
class生成规则优化 - 支持包声明和模块指令
- 错误消息指向用户代码行而非生成代码
孵化器特性
向量API(第九次孵化)
JDK16引入了JEP 338: Vector API (Incubator)提供了jdk.incubator.vector来用于矢量计算
JDK17进行改进并作为第二轮的incubatorJEP 414: Vector API (Second Incubator)
JDK18的JEP 417: Vector API (Third Incubator)进行改进并作为第三轮的incubator
JDK19的JEP 426:Vector API (Fourth Incubator)作为第四轮的incubator
JDK20的JEP 438: Vector API (Fifth Incubator)作为第五轮的incubator
JDK21的JEP 448: Vector API (Sixth Incubator)作为第六轮的incubator
JDK22的JEP 460: Vector API (Seventh Incubator)作为第七轮的incubator
JDK23的JEP 469: Vector API (Eighth Incubator)作为第八轮incubator
JDK24则作为第九轮incubator,与JDK23相比做了一些变动:比如引入了一个新的基于值的类Float16,用于表示IEEE 754二进制16格式的16位浮点数。
SIMD编程模型:
// 计算两个浮点数组的点积
void vectorComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i += FloatVector.SPECIES_512.length()) {
var va = FloatVector.fromArray(FloatVector.SPECIES_512, a, i);
var vb = FloatVector.fromArray(FloatVector.SPECIES_512, b, i);
var vc = va.mul(vb)
.add(va.lanewise(VectorOperators.POW, 2))
.add(vb.lanewise(VectorOperators.POW, 2));
vc.intoArray(c, i);
}
}硬件加速支持:
| 指令集 | 支持操作 | 加速比 |
|---|---|---|
| AVX-512 | 8x双精度浮点并行 | 6.8x |
| NEON | 4x单精度浮点并行 | 3.2x |
| SVE | 可变长度向量操作 | 4.5x |
实验特性
分代Shenandoah垃圾回收器提升吞吐量与响应速度
JEP 404: Generational Shenandoah
该JEP主要是提供了一个实验性的分代模式,将分代概念引入Shenandoah GC,通过区分新生代和老年代实现了显著的性能突破。传统Shenandoah作为全堆回收器,每次GC都需要扫描整个堆空间,而分代版本通过以下机制优化:
- 新生代专用回收策略:采用复制算法快速回收短生命周期对象,减少老年代扫描频率
- 卡表(Card Table)优化:精确记录老年代到新生代的跨代引用,降低GC停顿时间30%以上
- 并行标记增强:在并发标记阶段优先处理新生代区域,使平均GC暂停时间控制在2ms以内 实测表明,在16GB堆内存的微服务场景下,分代Shenandoah相比原版吞吐量提升40%,同时保持亚毫秒级的最大暂停时间,成为低延迟应用的理想选择。
紧凑对象头设计减少内存占用
JEP 450: Compact Object Headers
重构了Java对象的内存布局,将 HotSpot JVM中的对象标头大小从96到128位减少到64位。Java程序中的对象往往很小,作为 Project Lilliput的一部分进行的实验表明,许多工作负载的平均对象大小为 256 到 512 位(32 到 64 字节)。这意味着超过 20% 的实时数据可以单独由对象标头获取。因此,即使对象标头大小略有改进,也可以显著减少占用空间、数据局部性并减轻 GC压力。在实际应用程序中试用过Project Lilliput的早期采用者证实,内存占用通常会减少10%–20%。关键技术突破包括:
- 压缩锁标志位:将原有的2字节Mark Word压缩为1字节,保留基本锁状态和hashcode信息
- 类型指针优化:使用32位偏移量替代64位类指针,配合压缩类空间(Compressed Class Space)工作
- 对齐填充智能分配:根据CPU缓存行特性动态调整对象填充策略 在包含百万级对象的电商应用中,该特性减少堆内存使用15%,同时由于更好的缓存局部性,使整体吞吐量提升8%。需要注意的是,该特性要求所有依赖JOL(Java Object Layout)工具的分析代码进行相应适配。

