JDK 11 升级至 JDK 17

一、前言

今天小编翻看了一下 JDK 官网,发现 JDK 都已经在出到了 JDK25 了。然后我相信还是有很多企业和个人依旧在用的是 JDK8。

SpringBoot 3.x 对于 JDK 的最低版本要求是 17。SpringBoot 4.0.0 的快照版和预览版已经出了。

不可否认,JDK 8 是之前应用最广泛,服务时间最长的一个版本。但是 Java 开发中泰斗级的开发框架 Spring Framework 6 和 Spring Boot 3 对 JDK 版本的最低要求是JDK 17。相信 Spring 对 JDK 17 是经过深度验证的。为了使用 Spring 最新框架,很多团队和开发者就必须升级到 Java 17 版本。

小编在上一篇文章中已经着重聊了 JDK 8 升级至 JDK 11 的一些重要特性。今天小编将继续带大家梳理 JDK 11 升级至 JDK 17 的一些重要特性。

为了让大家更好的理解 JDK 11 升级到 JDK 17 带来的重要特性,本文将从以下 4 个方面进行讲解:

  • 升级 JDK 17 的必要性
  • 语言新特性
  • 新工具和库更新
  • JVM 优化

二、升级 JDK 17 必要性

1. 版本支持

JDK 11 作为一个 LTS版本,已于2023年9月结束公共更新, 对应的补丁和安全警告等支持将持续至 2026 年。而JDK 17 作为当前的LTS版本将提供至少到 2026 年的支持时间框架。

2. Spring 框架要求

现代框架已全面转向JDK 17,Spring Framework 6 和 Spring Boot 3 对 JDK 版本的最低要求是 JDK 17。

3. 性能提升

从JDK 11升级到JDK 17后:

  • G1GC(默认)平均速度提升 8.66%;
  • ParallelGC 提升 6.54%;
  • Parallel GC 整体比 G1 GC 快 16.39%;

简而言之,JDK17 更快,高吞吐量垃圾回收器比低延迟垃圾回收器更快。

这些性能改进直接转化为业务应用的处理能力提升和基础设施成本降低。

三、语言新特性

1. 记录类(Records)

Records 最早出现在 Java 14、15 中,作为预览特性引入的,直至在 Java 16 中成为正式版。

记录类是一种新型的类声明,专门用于充当不可变数据载体:

复制代码
// 代替传统的POJO类
public record User(String name, String email, int age) { }

// 自动生成构造函数、访问器、equals、hashCode和toString
User user = new User("张三", "zhangsan@example.com", 25);
System.out.println(user.name()); // 输出"张三"

如上述代码所示,Record类型自动提供了构造方法、访问器(getter)、equals()、hashCode()、toString()方法以及一个解析器(parser),无需定义Class。使用记录类可以减少约70%的样板代码,使代码更加清晰易懂。

2. 密封类(Sealed Classes)

这个特性从 Java 15 的预览版本晋升为正式版本。

密封类提供了对继承层次结构的精确控制:

复制代码
// 定义密封接口
public sealed interface Shape 
    permits Circle, Rectangle, Triangle {
    
    double area();
}

// 允许的子类必须明确修饰符
public final class Circle implements Shape {
    private final double radius;
    
    public Circle(double radius) { this.radius = radius; }
    
    @Override
    public double area() { return Math.PI * radius * radius; }
}

public non-sealed class Rectangle implements Shape {
    private final double width, height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double area() { return width * height; }
}

通过permits关键字明确声明允许继承的子类,增强了领域模型安全性,避免不受控的扩展。子类必须声明为final、sealed或non-sealed,确保继承关系的完整性。

3. instanceof 模式匹配增强

模式匹配(Pattern Matching)最早在 Java 14 中作为预览特性引入,直至 Java 16 中成为正式版。

对 instanceof 的改进,主要目的是为了让创建对象更简单、简洁和高效,并且可读性更强、提高安全性。

复制代码
// 旧的写法
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// 新写法
if (obj instanceof String s) {
    // s自动转换为String类型
    System.out.println(s.length()); 
}

4. Switch表达式增强

switch 表达式增强最早在 Java 12 中作为预览特性引入,直至 Java 14 中成为正式版本。

复制代码
// 新的switch表达式
String formatted = switch (obj) {
    case Integer i -> String.format("int: %d", i);
    case String s && !s.isEmpty() -> String.format("str: %s", s);
    case null -> "null value";
    default -> "unknown";
};

// 支持yield返回值
String description = switch (day) {
    case MONDAY, FRIDAY -> "工作日";
    case SATURDAY, SUNDAY -> {
        System.out.println("这是周末");
        yield "周末";
    }
    default -> "未知";
};

Switch表达式带来了简化式的编码方式,提供了新的分支切换方式,即 -> 符号,右则表达式方法体在执行完分支方法之后,自动结束switch分支。

5. 文本块(Text Blocks)

文本块最早在 Java 13 中作为预览特性引入,直至 Java 15 中成为正式版本。

文本块极大改善了多行字符串的处理:

复制代码
// JSON示例
String json = """
    {
      "name": "张三",
      "age": 30,
      "address": "北京市朝阳区"
    }
    """;

// SQL示例  
String query = """
    SELECT id, name, email
    FROM users
    WHERE status = 'ACTIVE'
    ORDER BY created_date DESC
    """;

文本块通过三引号语法定义,自动处理换行和缩进,使代码中的长字符串更易读和维护。

四、新工具和库更新

1. Socket API 重构

在 Java 13 中对 Socket API 做了重做。

原有的 java.net.Socket 和 java.net.ServerSocketAPI 及其底层实现,自 JDK 1.0 就已存在。这套实现混合了遗留的 Java 和 C 代码,维护和调试都非常痛苦。原有的 PlainSocketImpl 实现使用线程栈作为 I/O 缓冲区,这导致需要多次增加默认线程栈大小。它使用原生数据结构来支持异步关闭,这也成为了多年来可靠性和移植性问题的根源,并且还存在一些并发问题。

在 JDK 13 之前,通过使用 PlainSocketImpl 作为 SocketImpl 的具体实现。JDK 13 使用新的 NioSocketImpl 来替代原有的实现。

NioSocketImpl 的实现共享了 JDK 内部的 NIO(New I/O)架构,它不需要自己的原生代码,并且使用了现有的缓存机制,不再需要使用线程栈。新代码使用 java.util.concurrent 并发锁代替同步方法,因此避免了很多并发问题。

新实现在 Socket 进行需要 timeout 的操作(如connect,accept,read)时,会采用非阻塞模式来替代。

新实现使用 java.lang.ref.Cleaner 机制在 SocketImpl 被垃圾回收后关闭端口。连接重置处理与旧有机制一样,在连接重置后尝试读取端口会始终失败。

2. 改进 NullPointerException

最早在 Java 14 中引入。

JDK14 以前的出现 NullPointerException 时,只能定位到所在异常行,无法定位具体是哪个变量。改进后的 NullPointerException,可以清晰描述具体变量,提升了空指针异常的可读性。

开启方式:

复制代码
-XX:+ShowCodeDetailsInExceptionMessages

3. 伪随机数生成器

这个特性是为伪随机数生成器 RPNG(Pseudo-Random Number Generators)增加了新的接口类型和实现,可以更容易地互换使用不同的算法,而且它还为基于流的编程方式提供了更好的支持。这个特性的目标有四个:

  • 在应用程序中更容易地交替使用各种 PRNG 算法;
  • 改进了对基于流的编程的支持,提供了 PRNG 对象流;
  • 消除现有 PRNG 类中的重复代码;
  • 保留java.util.Random类的现有行为,做好向下兼容。

新增了java.util.random.RandomGenerator接口,作为所有 PRNG 算法的统一 API,提供了工厂类java.util.random.RandomGeneratorFactory,借助java.util.ServiceLoader.load()的能力加载各种 PRNG 算法实现,可以构造RandomGenerator实例。

我们遍历一下看看有哪些 PRNG 算法:

复制代码
RandomGeneratorFactory.all().forEach(factory -> {
    System.out.println(factory.group() + ":" + factory.name());
});

结果是:

复制代码
LXM:L32X64MixRandom
LXM:L128X128MixRandom
LXM:L64X128MixRandom
Legacy:SecureRandom
LXM:L128X1024MixRandom
LXM:L64X128StarStarRandom
Xoshiro:Xoshiro256PlusPlus
LXM:L64X256MixRandom
Legacy:Random
Xoroshiro:Xoroshiro128PlusPlus
LXM:L128X256MixRandom
Legacy:SplittableRandom
LXM:L64X1024MixRandom

Legacy:Random就是我们常用的java.util.Random,我们来试试看:

复制代码
RandomGenerator randomGenerator = RandomGeneratorFactory.of("Random")
        .create(System.currentTimeMillis());
System.out.println(randomGenerator.getClass());
System.out.println(randomGenerator.nextInt(10));

结果是:

复制代码
class java.util.Random
6 (这个值随不同的运行结果不同)

我们还可以使用流式编程方式批量获取随机数:

复制代码
final IntStream ints = RandomGeneratorFactory.of("L128X128MixRandom")
        .create()
        .ints(10, 0, 100);
System.out.println(Arrays.toString(ints.toArray()));

结果会得到 10 个随机数字数组(每次运行结果不同):

复制代码
[50, 16, 73, 4, 79, 32, 55, 34, 40, 53]

五、JVM与性能改进

1. ZGC 垃圾回收器

ZGC 最早在 Java 11 中作为实验性功能引入的,直至 Java 13 成为正式版本。

ZGC旨在实现亚毫秒级的最大暂停时间,即使处理TB级堆内存也能保持稳定。

相比于 Java 11,ZGC 在 Java 13 做了如下改进:

  • 释放未使用内存给操作系统
  • 支持最大堆大小为 16TB
  • 添加参数:-XX:SoftMaxHeapSize 来软限制堆大小

启用ZGC:

复制代码
java -XX:+UseZGC -Xmx4g -jar your-application.jar

ZGC 释放未使用内存的功能,默认情况下是开启的,可以通过参数:-XX:-ZUncommit 显式关闭。

如果将最小堆大小 (-Xms) 配置为等于最大堆大小 (-Xmx),则将隐式的禁用此功能。

通过配置参数:-XX:ZUncommitDelay = <seconds>(默认值 300 秒)实现延迟释放。

2. G1GC 持续优化

虽然G1仍然是默认垃圾回收器,但JDK 17中的 G1GC 包含了可中止的混合收集集合、NUMA 可识别内存分配等改进,进一步降低了暂停时间。

Java 14 改进非一致性内存访问(NUMA)系统上的 G1 垃圾收集器的整体性能,主要是对年轻代的内存分配进行优化,从而提高 CPU 计算过程中内存访问速度。

启动参数:

复制代码
-XX:+UseNUMA

通过这种方式来启用可识别的内存分配方式,能够提高一些大型计算机的 G1 内存分配回收性能。

3. 删除 CMS 垃圾回收器

CMS 是实现老年代垃圾回收算法(标记-清除的方式进行内存回收)的垃圾回收期。在执行内存回收时,可以与用户线程并发执行,,比较适合在追求 GC 速度的服务器上使用。

然后正是因为能够与用户线程并发执行这个特带你,CMS 存在如下几个缺点:

  • 然后正式因为可以与用户现场并发执行,在服务器 CPU 核数较小的情况下,容易造成比较高的系统负载。
  • 在执行用户线程时,会继续创建新的对象、也会继续释放不用的对象。在 CMS 垃圾回收器执行标记不可达内存后,并行执行的用户线程继续释放不可达的对象,这部分因为未被标记,所以无法在本轮回收周期内回收,只能等待下次回收。
  • 在执行用户现场时,会继续创建新的对象,所以 CMS 垃圾回收期在执行期间,需要预留一些空间用来保存用户新创建的对象,同时标记-清除算法势必会产生内存碎片,当内存碎片过多时,将会给大对象分配带来麻烦。

因此早在 Java 9 中,就已经决定弃使 CMS 回收器了,而这次在 Java 14 中,是彻底将其禁用,并删除与 CMS 有关的选项,删除与 CMS 有关的文档。曾经辉煌一度的 CMS 回收器,现已成为了历史。

4. 弃用 ParallelScavenge 和 SerialOld GC 的组合使用

由于组合使用 ParallelScavenge 和 SerialOld 这两种垃圾回收期,却要花费巨大工作量来进行维护,所以在 Java 14 版本中,考虑将这两 GC 的组合弃用。

5. 禁用偏向锁定

在 JDK 15 中,准备禁用和废除偏向锁,默认情况下禁用偏向锁,同时弃用所有相关的命令行选项。

6. Shenandoah(低暂停时间)垃圾收集器

Shenandoah 垃圾收集器最早在 Java 12 中作为实验性功能引入,此次终于变为产品特性。

Shenandoah 性能几乎和 ZGC 差不多,但是 ZGC 是 Oracle JDK 的,而 Shenandoah 只存在于 OpenJDK 中,使用 -XX:+UseShenandoahGC 命令行参数启用。

7. 弹性元空间

该特性能够实现将未使用的 metaspace 内存释放给操作系统,对于有大量类加载和卸载的应用程序是非常有帮助的。


🎁 福利时间

如果你正在备战面试或者想要学习其他知识,给大家推荐一个宝藏知识库,作者整理了一些列 Java 程序员需要掌握的核心知识,有需要的自取不谢。

知识库地址:https://farerboy.com/


相关推荐
指令集梦境1 小时前
图解:单调栈算法模板(Java语言)
java·开发语言·算法
IronMurphy1 小时前
多线程问!
java·jvm·spring
小灰灰搞电子1 小时前
C++ boost::circular_buffer 详解:原理、用法与实战
开发语言·c++·boost
vx-Biye_Design1 小时前
springboot安阳地区研学旅游服务小程序-计算机毕业设计源码12785
java·vue.js·windows·spring boot·tomcat·maven·mybatis
whaledown1 小时前
Kafka 与 Java 消息队列入门:用订单场景理解核心机制
java·kafka·消息队列·springboot
Moshow郑锴2 小时前
Ubuntu用SDKMAN轻松管理多个Java 版本
java·ubuntu·sdkman
阿昌喜欢吃黄桃2 小时前
RocketMq事务消息原理
java·中间件·消息队列·rocketmq·mq
CoderYanger2 小时前
A.每日一题:2095. 删除链表的中间节点
java·数据结构·程序人生·leetcode·链表·面试·职场和发展
Hanniel2 小时前
Python描述符(下):内置机制揭秘
开发语言·python·机器学习