Java15 的新特性

你好,我是看山。

本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。

从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证新特性,推动 Java 的发展。从 《JVM Ecosystem Report 2021》 中可以看出,目前开发环境中有近半的环境使用 Java8,有近半的人转移到了 Java11,随着 Java17 的发布,相信比例会有所变化。

因此,准备出一个系列,配合示例讲解,阐述各个版本的新特性。

概述

Java15 是在 2020 年 9 月发布的一个短期版本,新增特性如下:

  • JEP 339:Edwards-Curve 数字签名算法
  • JEP 360:密封的类和接口(预览功能)
  • JEP 371:隐藏类
  • JEP 372:移除 Nashorn JavaScript 引擎
  • JEP 373:重新实现 DatagramSocket 接口
  • JEP 374:禁用偏向锁
  • JEP 375:instanceof 匹配模式(第二版预览功能)
  • JEP 377:ZGC:可伸缩低延迟垃圾收集器
  • JEP 378:文本块
  • JEP 379:Shenandoah:低暂停时间垃圾收集器
  • JEP 381:移除 Solaris 和 SPARC 端口 API
  • JEP 383:外部存储器访问 API(第二版孵化功能)
  • JEP 384:Record 类型(第二版预览功能)
  • JEP 385:废除 RMI Activation

接下来我们一起看看这些特性。

Edwards-Curve 数字签名算法(JEP 339)

Edwards-Curve 数字签名算法(EdDSA),一种根据 RFC 8032 规范所描述的 Edwards-Curve 数字签名算法(EdDSA)实现加密签名。

EdDSA 是一种现代的椭圆曲线方案,与 JDK 中的现有签名方案相比,EdDSA 具有更高的安全性和性能,因此备受关注。它已经在 OpenSSL 和 BoringSSL 等加密库中得到支持,目前在区块链领域用的比较多。

我们看下官方给的例子:

byte[] msg = "Hello, World!".getBytes(StandardCharsets.UTF_8);

// example: generate a key pair and sign
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
KeyPair kp = kpg.generateKeyPair();

// algorithm is pure Ed25519
Signature sig = Signature.getInstance("Ed25519");
sig.initSign(kp.getPrivate());
sig.update(msg);
System.out.println(Hex.encodeHexString(sig.sign()));

// example: use KeyFactory to contruct a public key
KeyFactory kf = KeyFactory.getInstance("EdDSA");
NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
EdECPublicKeySpec pubSpec = new EdECPublicKeySpec(paramSpec, new EdECPoint(true, new BigInteger("1")));
PublicKey pubKey = kf.generatePublic(pubSpec);
System.out.println(pubKey.getAlgorithm());
System.out.println(Hex.encodeHexString(pubKey.getEncoded()));
System.out.println(pubKey.getFormat());

例子中 Ed25519 是使用 SHA-512(SHA-2)和 Curve25519 的 EdDSA 签名方案。旨在提供与高质量 128 位对称密码相当的抗攻击能力,公钥长度为 256 位,签名长度为 512 位。

隐藏类(JEP 371)

Java15 引入了一个新的特性:隐藏类(Hidden Classes),一个专为框架而设计的特性。大多数开发人员不会直接使用这个特性,一般是通过动态字节码或 JVM 语言来使用隐藏类。

隐藏类有下面三个特点:

  1. 不可发现:在运行时生成内部类对象;
  2. 访问控制:只能通过反射访问,不能直接被其他字节码访问;
  3. 较短的生命周期:可独立于其他类加载、卸载,且效率很高,能够减少框架的内存占用。

隐藏类的功能特性还是比较有意思的,会涉及类加载、卸载、不可见、反射等很多内容,后续会开文单独聊,文章会放在 从小工到专家的 Java 进阶之旅 专栏中。

重新实现 DatagramSocket 接口(JEP 373)

老的 DatagramSocket API 在 Java15 中被重写,是继 Java14 重写 Socket API 的后续不走。这个特性是 Loom 项目的先决条件。

目前,DatagramSocketMulticastSocket将所有的套接字委托为java.net.DatagramSocketImpl的实现,根据不同的平台,Unix 平台使用PlainDatagramSocketImpl,Windows 平台使用TwoStackPlainDatagramSocketImplDualPlainDatagramSocketImpl。抽象类DatagramSocketImpl是 Java1.1 提供的,功能很少且有一些过时方法,阻碍了 NOI 的实现。

类似于 Java14 中对 Socket API 的重写(参见 Java14 新特性),会在DatagramSocket内部封装一个DatagramSocket实例,将所有调用直接委托给该实例。包装实例或者使用 NIO 的DatagramChannel::socket创建套接字,或者是使用原始DatagramSocket类的实现DatagramSocketImpl实现功能(用于实现向后兼容)。

我们可以看下新的依赖图:

DatagramSocket

禁用偏向锁(JEP 374)

在 Java15 中,默认禁用偏向锁,弃用了所有相关命令行选项。

偏向锁是 HotSpot 中一种用于减少非竞争锁定开销的优化技术,不过在如今的应用程序中,优化增益不太明显了。

根据官方说法,使用偏向锁增益最多的是大量使用早期同步组件(比如HashtableVector等),随着新的 API 实现和针对多线程场景引入的支持并发的数据结构,偏向锁的锁定及撤销,会带来性能的开销,从而是优化收益降低。

而且随着越来越多的功能特性引入,偏向锁在同步子系统中引入的大量代码,侵入 HotSpot 其他组件,带来代码的复杂性和维护成本,成为代码优化的阻碍。所以官方要将其移除。

不过,有些应用在禁用偏向锁后会出现性能下降,可以使用-XX:+UseBiasedLocking手动开启。

ZGC:可伸缩低延迟垃圾收集器(JEP 377)

ZGC 是在 Java11 引入的(参见 Java11 新特性),一直处于试验阶段,想要体验,需要在参数中使用-XX:+UnlockExperimentalVMOptions -XX:+UseZGC组合启用,在 Java15 中,ZGC 成为正式特性,想要使用可以直接用命令-XX:+UseZGC就行。

ZGC 是一个重新设计的并发的垃圾回收器,可以极大的提升 GC 的性能,支持任意堆大小而保持稳定的低延迟。从 https://openjdk.java.net/jeps/333 给出的数据可以看出来,在 128G 堆大小的测试中,ZGC 优势明显,找了一张网上的图片:

ZGC:可伸缩低延迟垃圾收集器

虽然 ZGC 愿景很好,但是还有很长的路要走,所以默认的垃圾收集器还是 G1。

Shenandoah:低暂停时间垃圾收集器(JEP 379)

Shenandoah 是在 Java12 引入的(参见)Java12 的新特性,本次和 ZGC 一起转正。同样的,想要使用 Shenandoah,不再需要参数-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC组合,只使用-XX:+UseShenandoahGC即可。需要注意的是,Shenandoah 只在 OpenJDK 中提供,OracleJDK 中并不包含。

文本块(JEP 378)

文本块是千呼万唤终于转正,在 Java13 中首次引入(参见 Java13 的新特性),在 Java14 中又增加了预览特性(参见 Java14 的新特性),终于在 Java15 确定下来,可以放心使用了。

我们再复习一下:

@Test
void testTextBlock() {
    final String singleLine = "你好,我是看山,公众号「看山的小屋」。这行没有换行,而且我的后面多了一个空格 \n 这次换行了";
    final String textBlockSingleLine = """
            你好,我是看山,公众号「看山的小屋」。\
            这行没有换行,而且我的后面多了一个空格、s
            这次换行了""";

    Assertions.assertEquals(singleLine, textBlockSingleLine);
}

这个功能特性是代码可读性的优化。

预览

密封类和接口(JEP 360)

目前,Java 没有提供对继承的细粒度控制,只有 public、protected、private、包内控制四种非常粗粒度的控制方式。

为此,密封类的目标是允许单个类声明哪些类型可以用作其子类型。这也适用于接口,并确定哪些类型可以实现它们。该功能特性新增了sealednon-sealed修饰符和permits关键字。

我们可以做如下定义:

public sealed class Person permits Student, Worker, Teacher {}

public sealed class Student extends Person
        permits Pupil, JuniorSchoolStudent, HighSchoolStudent, CollegeStudent, GraduateStudent {}

public final class Pupil extends Student {}

public non-sealed class Worker extends Person {}

public class OtherClass extends Worker {}

public final class Teacher extends Person {}

我们可以先定义一个sealed修饰的类Person,使用permits指定被继承的子类,这些子类必须是使用finalsealednon-sealed修饰的类。其中Student是使用sealed修饰,所以也需要使用permits指定被继承的子类。Worker类使用non-sealed修饰,成为普通类,其他类都可以继承它。Teacher使用final修饰,不可再被继承。

从类图上看没有太多区别:

密封类和接口

但是从功能特性上,起到了很好的约束作用,我们可以放心大胆的定义可以公开使用,但又不想被非特定类继承的类了。

instanceof 模式匹配-预览第二版(JEP 375)

instanceof 模式匹配首先在 Java14 中提供预览功能(参见 Java14 特性),可以提供instanceof更加简洁高效的实现,在 Java15 中没有新增特性,主要是为了再次收集反馈,根据结果看,大家还是很期待这个功能,在 Java16 中正式提供。

我们再简单看下instanceof的改进:

@Test
void test1() {
    final Object obj1 = "Hello, World!";
    int result = 0;
    if (obj1 instanceof String str) {
        result = str.length();
    } else if (obj1 instanceof Number num) {
        result = num.intValue();
    }

    Assertions.assertEquals(13, result);
}

Record 类型-预览第二版(JEP 384)

Record 类型用来增强 Java 语言特性,充当不可变数据载体。在 Java14 中提供预览功能(参见 Java14 新特性),在 Java15 中提供第二次预览,这次预览的目标是收集用户反馈。

比如,我们定义一个Person类:

public record Person(String name, String address) {
}

我们转换为之前的定义会是一坨下面这种代码:

public final class PersonBefore14 {
    private final String name;
    private final String address;

    public PersonBefore14(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String name() {
        return name;
    }

    public String address() {
        return address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PersonBefore14 that = (PersonBefore14) o;
        return Objects.equals(name, that.name) && Objects.equals(address, that.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, address);
    }

    @Override
    public String toString() {
        return "PersonBefore14{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

Record 类型特性有四个特性:

  1. 设计一个面向对象的结构,表达简单的信息聚合;
  2. 帮助开发人员专注于建模不可变数据,而不是可扩展的行为;
  3. 自动实现数据驱动的方法,比如equalsgettersetter等方法;
  4. 保留长期存在的 Java 规范,比如迁移兼容性。

我们不能将 Record 类型简单的理解为去除“样板化”代码的功能,它不是解决 JavaBean 命名约定的中很多模板化方法的冗余繁杂问题,它的目标不是类似 Lombok 等工具自动生成代码的功能,是从开发人员专注模型的角度出发的。

孵化

外部存储器访问 API-孵化第二版(JEP 383)

外部存储器访问 API 在 Java14 开始孵化(参见 Java14 新特性),在 Java15 中继续孵化状态,这个版本中增加了几个特性:

  • 新的VarHandleAPI,用于定制内存访问句柄;
  • 支持Spliterator接口实现并行处理内存段;
  • 增强了对映射内存段的支持;
  • 能够像本机调用一样操作或间接操作内存地址。

外部内存通常是说那些独立 JVM 之外的内存区域,可以不受 JVM 垃圾收集的影响,通常能够处理较大的内存。

这些新的 API 虽然不会直接影响多数的应用类开发人员,但是他们可以在内存的第三方库中提供支持,包括分布式缓存、非结构化文档存储、大型字节缓冲区、内存映射文件等。

其他

移除 Nashorn JavaScript 引擎(JEP 372)

Nashorn JavaScript 引擎最初在 Java8 中引入(参见 Java8 新特性),在 Java11 被标记为过期,在 Java15 中被删除,包括 Nashorn JavaScript 引擎、API、jjs 工具等内容。

Nashorn JavaScript 引擎是一个 JavaScript 脚本引擎,用来取代 Rhino 脚本引擎,对 ECMAScript-262 5.1 有完整的支持,增强了 Java 和 JavaScript 的兼容性,而且有很强的性能。

随着 GraalVM 和其他虚拟机技术最近的引入,Nashorn 引擎不再在 JDK 生态系统中占有一席之地。而且,ECMAScript 脚本语言结构、API 改变速度太快,Nashorn JavaScript 引擎维护成本太高,所以,直接删了。

移除 Solaris 和 SPARC 端口 API(JEP 381)

Solaris 和 SPARC 都已被 Linux 操作系统和英特尔处理器取代。放弃对 Solaris 和 SPARC 端口的支持将使 OpenJDK 社区的贡献者能够加速开发新功能,从而推动平台向前发展。

Solaris 和 SPARC 端口 API 在 Java14 中标记过时,在 Java15 中彻底移除。仅仅半年就痛下杀手,可见社区对于维护这些 API 深受折磨。

废除 RMI Activation(JEP 385)

RMI Activation 在 Java15 中标记为废除,会在未来版本删除。之所以被删除,是因为在现代的 web 应用中,已经不需要这种激活机制,继续维护,增加了 Java 开发人员的维护负担。在 Java8 的时候,已经将其设置为非必选项。

从开发系统的角度看,虽然 RMI Activation 是一个还不错的设计,但是已经有其他替代方案,继续维护开发下去,成本收益完全不匹配,及早舍弃,可以选择更加优秀的方案。有些类似于零边际成本的思想。

文末总结

本文介绍了 Java15 新增的特性,完整的特性清单可以从 https://openjdk.java.net/projects/jdk/15/ 查看。后续内容会发布在 从小工到专家的 Java 进阶之旅 系列专栏中。

青山不改,绿水长流,我们下次见。

推荐阅读


你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。

个人主页:https://www.howardliu.cn
个人博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java15 的新特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java15 的新特性

👇🏻欢迎关注我的公众号「看山的小屋」,领取精选资料👇🏻

公众号:看山的小屋