JDK21是javaSE平台最新的长期支持版本。
JDK 21 Release Notes, Important Changes, and Information
JavaSE平台采用了基于时间的发布模型,JDK每六个月发布一次。
自JDK10及以后,版本字符串的格式反映了JavaSE平台基于时间的发布模型。$FEATURE.$INTERIM.$UPDATE.$PATCH .
$FEATURE为每个功能发布增加的版本号。该功能发布包含新的功能和对现有功能的更改,由JavaSE平台规范指定。版本号每六个月递增一次。例如,2018年3月v的版本号是10,2018年9月v的版本号是11,等等。
$INTERIM为每个临时版本添加的版本号,其中包含错误修复和增强。临时版本不包含不兼容的更改、特性清除,也不包含对标准API的任何更改。临时版本的版本号总是0(0),因为6个月版本模型不包括临时版本。但是,这个版本号是为将来的临时版本保留的,如果有的话。
$UPDATE 为更新版本增加的版本号,其中包括新功能中的安全问题、回归和错误的修复。该版本编号在$FEATURE 每三个月释放一次。例如,10月更新版的完整版本字符串是 21 .1月份更新版的完整版本字符串是 21 .0.2,等等。
$PATCH用于紧急补丁发布以修复一个关键问题的递增版本号。
版本字符串没有尾随零元素。例如,如果$FEATURE 是 21 ,的价值$INTERIM 是0,值$UPDATE 它的价值是1$PATCH 是0,那么完整版本字符串是 21 .0.1.
Java 21 还提供了数十个新功能和增强功能,其中 15 个增强功能的重要性足以保证他们自己的JDK 增强提案 - JEP ,涵盖 6 个预览功能和 1 个孵化器功能。
JDK 21 Release Notes, Important Changes, and Information
虚拟线程是轻量级线程,可以减少编写、维护和调试高吞吐量并发应用程序的工作量。
Oracle官方文档的机器翻译:
平台线程是作为操作系统(OS)线程的瘦包装器实现的。
平台线程在其底层操作系统线程上运行Java代码,平台线程在平台线程的整个生命周期内捕获其操作系统线程。
因此,可用平台线程的数量受限于操作系统线程的数量。
平台线程通常有一个大的线程堆栈和其他由操作系统维护的资源。
平台线程支持线程局部变量。
平台线程适合运行所有类型的任务,但可能是有限的资源。
Oracle官方文档的机器翻译:
与平台线程一样,虚拟线程也是 java.lang.Thread 的一个实例。
但是,虚拟线程并不依赖于特定的操作系统线程。
虚拟线程仍然在操作系统线程上运行代码。
但是,当虚拟线程中运行的代码调用阻塞 I/O 操作时,Java 运行时会挂起虚拟线程,直到可以恢复为止。
与挂起的虚拟线程关联的操作系统线程现在可以自由地为其他虚拟线程执行操作。
实现原理
虚拟线程的实现方式与虚拟内存类似。
为了模拟大量内存,操作系统将较大的虚拟地址空间映射到有限的 RAM。
同样,为了模拟大量线程,Java运行时将大量虚拟线程映射到少量操作系统线程。
与平台线程不同,虚拟线程通常具有浅调用堆栈,只执行单个 HTTP 客户端调用或单个 JDBC 查询。
尽管虚拟线程支持线程局部变量,但您应该仔细考虑使用它们,因为单个 JVM 可能支持数百万个虚拟线程。
虚拟线程适合运行大部分时间处于阻塞状态、通常等待 I/O 操作完成的任务。
但是,它们不适用于长时间运行的 CPU 密集型操作。
Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
thread.join();
try {
Thread.Builder builder = Thread.ofVirtual().name("MyThread");
Runnable task = () -> {
System.out.println("Running thread");
};
Thread t = builder.start(task);
System.out.println("Thread t name: " + t.getName());
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
下面的示例创建和启动两个虚拟线程
public class CreateNamedThreadsWithBuilders {
public static void main(String[] args) {
try {
Thread.Builder builder =
Thread.ofVirtual().name("worker-", 0);
Runnable task = () -> {
System.out.println("Thread ID: " +
Thread.currentThread().threadId());
};
// name "worker-0"
Thread t1 = builder.start(task);
t1.join();
System.out.println(t1.getName() + " terminated");
// name "worker-1"
Thread t2 = builder.start(task);
t2.join();
System.out.println(t2.getName() + " terminated");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这个例子打印输出类似于下列:
Thread ID: 21
worker-0 terminated
Thread ID: 24
worker-1 terminated
JDK 21: The GCs keep getting better
当涉及到原始吞吐量性能时,JDK17以来的收益并没有那么大,但仍略有增加。但是下面的图表中有两件事值得关注。首先,JDK8和最近的G1和并行JDKK之间的显著差异。从性能的角度来看,离开JDK8比现在更有好处。
使用一代ZGC时,10%的改进。ZGC中的新一代支持允许它更有效地回收内存,而不需要为每个GC考虑整个堆。效果是减少了CPU资源的花费,这些资源可以被应用程序用来提高其性能。
正如你所看到的,与JDK8相比,JDK21的性能显著地好。因此,如果您还在使用JDK8,您应该开始寻找升级。当升级的时候,也是重新评估使用哪个GC的好时机。如果搬到JDK21,我真的鼓励尝试代代相传的ZGC。在JDK21中,ZGC的代代版本和遗留模式都是可用的,要使用代代,您需要指定这两个标记:
-XX:+UseZGC -XX:+ZGenerational
文章中放了很多官网的链接推荐自己去学习。