写点什么

Java21 上手体验 - 分代 ZGC 和虚拟线程 | 京东云技术团队

  • 2023-10-09
    北京
  • 本文字数:3244 字

    阅读完需:约 11 分钟

Java21上手体验-分代ZGC和虚拟线程 | 京东云技术团队

一、导语

几天前 Oracle 刚刚发布了 Java21,由于这是最新的 LTS 版本,引起了大家的关注。我也第一时间在个人项目中进行了升级体验。一探究竟,和大家分享。

二、Java21 更新内容介绍

官方 release 公告:

https://jdk.java.net/21/release-notes

开源中国介绍:

https://my.oschina.net/waylau/blog/10112170

新特性一览:

  • JEP 431:序列集合

  • JEP 439:分代 ZGC

  • JEP 440:记录模式

  • JEP 441:switch 模式匹配

  • JEP 444:虚拟线程

  • JEP 449:弃用 Windows 32 位 x86 移植

  • JEP 451:准备禁止动态加载代理

  • JEP 452:密钥封装机制 API

  • JEP 430:字符串模板(预览)

  • JEP 442:外部函数和内存 API(第三次预览)

  • JEP 443:未命名模式和变量(预览)

  • JEP 445:未命名类和实例主方法(预览)

  • JEP 446:作用域值(预览)

  • JEP 453:结构化并发(预览)

  • JEP 448:Vector API(孵化器第六阶段)


其中大家比较关注的是分代 ZGC 和虚拟线程。

三、开箱

下载地址:

OpenJDK 版本:https://jdk.java.net/21/

Oracle 版本:https://www.oracle.com/java/technologies/downloads/

对比 17

边框由不锈钢升级为钛金属


目录结构一致:



模块数量比 17 少一个:



整体大小从 289MB 增加到了 320MB

四、升级体验

下载

更新 pom

尝试运行

运行报错:


java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'


解决办法:


升级 lombok 至 1.18.30


原因:https://github.com/projectlombok/lombok/issues/3393

兼容性检查:

由于我的项目以前用的 JDK17,本次升级兼容性良好,只发现了一处:


系统托盘中 使用了 PopupMenu,出现了字符集问题:


五、分代 ZGC 体验

ZGC 在之前的 JDK 版本中也有,这次的分代 ZGC 更是被大家看好,官方的介绍如下:


Applications running with Generational ZGC should enjoy:

Lower risks of allocations stalls,

Lower required heap memory overhead, and

Lower garbage collection CPU overhead.

Enable Generational ZGC with command line options -XX:+UseZGC -XX:+ZGenerational


性能测试参考:


https://inside.java/2023/09/03/roadto21-performance/

JVM 参数:-XX:+UseZGC -XX:+ZGenerational

使用 Java21,未使用 ZGC

MooInfo 内存占用查看



使用 Java21,使用分代 ZGC

MooInfo 内存占用查看




以上只是初步体验,关于 ZGC 的更多内容,如详细的分代回收情况后续进一步探索。


以上内存占用查看使用我之前做的一个工具,MooInfo:


https://github.com/rememberber/MooInfo


六、虚拟线程探索

Virtual threads are lightweight threads that reduce the effort of writing, maintaining, and debugging high-throughput concurrent applications.


虚拟线程是轻量级线程,可以减少编写、维护和调试高吞吐量并发应用程序的工作量。


Oracle 介绍原文:


https://docs.oracle.com/en/java/javase/20/core/virtual-threads.html#GUID-DC4306FC-D6C1-4BCC-AECE-48C32C1A8DAA

平台线程

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(); } }}
复制代码


或者


        try (ExecutorService myExecutor =            Executors.newVirtualThreadPerTaskExecutor()) {            Future<?> future =                myExecutor.submit(() -> System.out.println("Running thread"));            future.get();            System.out.println("Task completed");        } catch (InterruptedException | ExecutionException e) {            e.printStackTrace();        }   
复制代码


以上是 Java20 文档的用法,实际使用时我发现还可以这样:


 Thread.startVirtualThread(() -> {                // do something                           });
复制代码

平台线程和虚拟线程对比测试

为了测试对比,我建了一个项目



初步对比,和官网描述一致,计算密集型场景差别不大,IO 密集型场景有明显改善


虚拟线程 100 个,IO 读文件



平台线程 100 个,IO 读文件



虚拟线程 100 个,Get 请求百度首页



平台线程 100 个,Get 请求百度首页



但是由于是本地测试,且用例比较简陋,无法完全得出准确结论。


日后大家有实际 IO 密集性多线程场景可以实际感受下。

线程池?忘了它吧

开发人员通常会将应用程序代码从基于线程池的传统 ExecutorService 迁移到虚拟线程每任务 ExecutorService。


线程池和所有资源池一样,旨在共享昂贵的资源,


但虚拟线程并不昂贵,而且永远不需要将它们池化。

七、一颗语法糖?Java21 新特性:Record Patterns

一个例子感受一下新特性:Record Patterns


before:


static void printSum(Object obj) {    if (obj instanceof Point p) {        int x = p.x();        int y = p.y();        System.out.println(x+y);    }}
复制代码


after:


static void printSum(Object obj) {    if (obj instanceof Point(int x, int y)) {        System.out.println(x+y);    }}
复制代码


参考:https://my.oschina.net/didispace/blog/10112428


作者:京东科技 周波

来源:京东云开发者社区  转载请注明来源

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
Java21上手体验-分代ZGC和虚拟线程 | 京东云技术团队_Java_京东科技开发者_InfoQ写作社区