作者:牧思 & 山猎
今年的 9 月 19 日,作为最新的 LTS (Long Term Support) Java 版本,Java 21 正式 GA,带来了不少重量级的更新,详情请参考 The Arrival of Java 21 [ 1] 。虽然目前 Java 11 和 Java 17 都还没有在国内大规模普及,Java 8 依然占据主流地位,但及时更新 JDK 版本可以为开发者带来许多重要的价值,包括应用程序性能和稳定性上的提升,以及可以帮助提升生产力的新功能。作为亚洲地区最有影响力的可观测以及 APM 服务提供方,阿里云 ARMS 团队也第一时间响应 Java 21 的 GA 发布,率先对 Java 21 进行了适配,帮助用户更好的观测 Java 21 应用!
Java 21 带来了 15 个新特性,包括虚拟线程、分代式 ZGC 等重磅功能,以及其他方面的优化,让我们先睹为快,体验一下这些新特性:
虚拟线程 (Virtual Threads) 绝对是 Java 21 中最重量级的新特性,此前在 Java 版本中,每一个 java.lang.Thread 对象都只对应一个操作系统内核中的线程,而线程在操作系统又是一种相对昂贵的系统资源:线程的创建、切换、销毁等操作都需要进入到内核态。在高并发的场景下,如果创建大量线程来处理请求,将会导致多线程被频繁的挂起和切换,非常消耗系统资源。
虚拟线程则是一种轻量级的用户态线程,与传统线程由 OS 调度运行不同,虚拟线程是由 JDK 底层调度运行的,其创建、调度、销毁等操作全部由用户空间的库函数来完成,也就是说虚拟线程与内核中的线程的对应关系是 M:N 的,如图 1 所示:
图 1:线程与虚拟线程关系图
因此,在高并发场景下,创建大量虚拟线程来处理请求的开销,相比创建大量线程来说将会降低很多。在 Java 程序中,线程与虚拟线程的对比如表 1 所示:
表 1:线程与虚拟线程的对比表
在 Java 21 中,可以通过以下几种方法创建虚拟线程并运行:
// 方式一:
Thread vt = Thread.startVirtualThread(() -> {});
// 方式二:
Thread.ofVirtual().unstarted(() -> {});
vt.start();
// 方式三:
ThreadFactory tf = Thread.ofVirtual().factory();
Thread vt = tf.newThread(() -> {});
vt.start();
// 方式四:
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
ThreadFactory tf = Thread.ofVirtual().factory();
Thread vt = tf.newThread(() -> {});
executor.submit(vt);
在 Java 21 中,增加了对 ZGC 的分代支持 (Generational ZGC) 以提高垃圾回收的性能。在此之前,ZGC 是没有分代概念的,每次运行时都会去收集所有的对象,不会考虑对象的年龄。但根据分代收集理论,绝大部分对象都是朝生夕灭的,并且熬过越多次垃圾收集过程的对象会越难死亡。清理年轻代对象需要的资源更少,能清理出更多的内存;反之,清理老年代对象需要的资源更多,能清理出的内存更少。这就意味着,ZGC 可以基于分代收集理论,更进一步的提升垃圾回收效率。于是在 Java 21,分代收集机制被引入,以更频繁的收集年轻代对象,这对于使用 ZGC 的 Java 应用的性能提升,有非常大的帮助。
在 Java 21 上使用 ZGC,首先需要在 Java 启动命令中加入 -XX:+UseZGC 选项开启 ZGC。默认情况下,添加该选项后启用的是非分代的 ZGC,如需使用分代式 ZGC,则需要再额外添加 -XX:+ZGenerational 选项。
# 使用分代式ZGC
$ java -XX:+UseZGC -XX:+ZGenerational ...
除了虚拟线程和分代 ZGC,Java 21 还引入了其他有意思的特性,比如:
void main() {
System.out.println("Hello, World!");
}
具体的其它特性,可以参考:openjdk.org/projects/jdk/21/ 。
ARMS 最新的 3.1.0 版本探针中,我们对 Java 21 进行了支持,开发者可以参考此文开启 Java 21 应用的可观测之旅。
首先需要下载安装 JDK 21,可以从 Oracle 等厂商的官网下载安装,也可以通过 sdkman 等三方工具进行下载安装。
安装完 JDK 21 后,可以参考以下代码,编写一个简单的 SpringBoot 3.x 应用,该应用中使用了 Java 21 的 Record Patterns 特性,帮助用户用简洁的语法解构 Java 中的 record 对象:
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.0.6
xxx
xxx
xxx
xxx
21
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-maven-plugin
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
record Point(int x, int y) {
}
@RestController
static class DemoController {
@RequestMapping("/demo")
String demo(@RequestParam String type) {
Object object = null;
if ("record".equals(type)) {
object = new Point(1, 2);
} else {
object = "object";
}
return solve(object);
}
private String solve(Object object) {
if (object instanceof Point(int x, int y)) {
return "Point: " + x + ":" + y;
}
return "Invalid Point";
}
}
}
对于 Java 应用,ARMS 提供了多种便捷接入方式,您可以参考应用监控接入概述 [ 2] 进行接入。对于运行在阿里云容器服务 ACK [ 3] 中的应用,可以拥有最简单的接入方式:基于 Pilot 模式实现探针的自动注入以及配置,无需修改镜像,只需要在应用 Yaml 中加入 2 个 Pod Label 就能接入 ARMS。
首先为 ACK 集群安装 ack-onepilot 组件,如图2所示:
图 2:安装 ack-onepilot
安装完成之后,在 ACK 集群内创建 Java 应用,并在 pod 的 spec.template.metadata 字段中添加图 3 中的两个 Pod 标签。其中 armsPilotCreateAppName 代表接入到 ARMS 的应用名,可以和 Deployment 名保持一致,armsPilotAutoEnable 设置为 on 即可。您也可以直接编辑 Deployment 的 Yaml 文件添加 Pod 标签。
图 3:添加标签
应用成功部署后,在 ACK 控制台上会展示 ARMS 控制台链接,如图 4 所示,点击就能跳转到 ARMS 控制台。
图 4:ARMS 控制台操作选项
Java 启动应用后,应用也将会打印如图 5 所示的日志,说明应用已经挂载上 ARMS 的探针。
图 5:JDK21 应用启动日志
说明:ARMS 对 Java 21 的支持依赖于 3.1.0 版本探针,截至本文发表之日,3.1.0 版本探针还没有正式发布,您可以钉钉扫码加入 ARMS 支持 Java 21 体验群,获取 3.1.0 版本探针。在 3.1.0 版本正式发布后,您就可以直接在 ARMS 官网下载最新版探针,或者通过通过 Pilot 模式自动获取 3.1.0 版本探针。
应用启动成功后,请求 /demo 接口,将会得到图 7 所示的响应:
图 7:/demo 接口响应
在请求完 /demo 接口后,可前往 ARMS 控制台的应用列表页面点击进入应用的监控页面,如图 8 所示,或者直接在 ACK 控制台上通过转接跳转进入 ARMS 控制台。
图 8:ARMS 控制台应用列表
可以看到,ARMS 已经成功识别到 Java 21 应用,并收集相关可观测数据,如图 9-11 所示,这些观测数据可帮助用户快速洞悉系统运行状况,加速线上问题排查效率,提升业务运行稳定性。更多 ARMS 应用监控的重要功能,比如智能洞察、调用链分析、CPU &内存诊断,请参考 ARMS 应用监控帮助文档 [ 4] 。
图 9:ARMS 应用元信息
图 10:ARMS 应用接口调用信息
图 11:ARMS 应总览信息
图 12: Tracing 监控盲区示例图
ARMS 代码热点功能在业界知名的开源持续剖析工具 Async Profiler [ 6] 基础上,通过关联调用链中的 TraceId & SpanId 信息提供了调用链级别的 On & Off-CPU 火焰图,可有效对 Tracing 的监控盲区细节进行还原,帮助用户诊断各类常见的慢调用链问题。
图 13: ARMS 支持代码热点功能效果图
更多功能介绍和使用细节请参考慢调用链诊断利器-ARMS 代码热点。
ARMS 代码热点体验交流钉钉群群号:22560019672
更多 ARMS 产品家族的详细介绍,请参考 ARMS 官方帮助文档 [ 7] 。
参考资料:
[1] https://openjdk.org/projects/jdk/21/
[2] https://openjdk.org/jeps/439
[3] https://openjdk.org/jeps/440
[4] https://blogs.oracle.com/java/post/the-arrival-of-java-21
相关链接:
[1] The Arrival of Java 21
https://blogs.oracle.com/java/post/the-arrival-of-java-21
[2] 应用监控接入概述
https://help.aliyun.com/zh/arms/application-monitoring/getting-started/overview
[3] 容器服务 ACK
https://www.aliyun.com/product/kubernetes**
[4] ARMS 应用监控帮助文档
https://help.aliyun.com/zh/arms/application-monitoring/product-overview/functional-characteristics
[5] ARMS 支持的 Java 组件和框架
https://help.aliyun.com/zh/arms/application-monitoring/developer-reference/java-components-and-frameworks-supported-by-arms
[6] Async Profiler
https://github.com/async-profiler/async-profiler
[7] ARMS 官方帮助文档
https://help.aliyun.com/zh/arms/