Java 16 在 2021 年 3 月 16 日正式发布,不是长久支持版本,这次更新没有带来很多语法上的改动,但是也带来了不少新的实用功能。
OpenJDK Java 16 下载:https://jdk.java.net/archive/
OpenJDK Java 16 文档:https://openjdk.java.net/projects/jdk/16/
此文章属于 Java 新特性教程 系列,会介绍 Java 每个版本的新功能,可以点击浏览。
1. JEP 347: 启用 C++ 14 语言特性
这项更新和 Java 开发者关系不太密切,JEP 347 允许 在 JDK 的 C++ 源码中使用 C++ 14 的语言特性,并且给出了哪些特性可以在 HotSpot 代码中使用的具体说明。
扩展阅读:启用 C++ 14 语言特性
2. JEP 357:从 Mercurial 迁移到 Git
在此之前,OpenJDK 源代码是使用版本管理工具 Mercurial 进行管理的,你也可以在 http://hg.openjdk.java.net/ 查看 OpenJDK 的源代码历史版本。
但是现在迁移到了 GIt ,主要原因如下:
- Mercurial 生成的版本控制元数据过大。
- Mercurial 相关的开发工具比较少,而 Git 几乎在所有的主流 IDE 中已经无缝集成。
- Mercurial 相关的服务比较少,无论是自建托管,还是服务托管。
为了优雅的迁移到 Git,OpenJDK 做了如下操作。
- 将所有的单存储库 OpenJDK 项目从 Mercurial 迁移到 Git。
- 保留所有的版本控制历史,也包括 Tag。
- 根据 Git 的最佳实践重新格式化提交的消息。
- 创建了一个工具用来在 Mercurial 和 Git 哈希之间进行转换。
扩展阅读: 从 Mercurial 迁移到 Git
3. JEP 369:迁移到 GitHub
和 JEP 357 从 Mercurial 迁移到 Git 的改变一致,在把版本管理迁移到 Git 之后,选择了在 GitHub 上托管 OpenJDK 社区的 Git 仓库。不过只对 JDK 11 以及更高版本 JDK 进行了迁移。
4. JEP 376:ZGC 并发线程堆栈处理
这次改动让 ZGC 线程堆栈处理从**安全点(Safepoints)**移动到并发阶段。
如果你忘记了什么是 Safepoints,可以复习一下。
我们都知道,在之前,需要 GC 的时候,为了进行垃圾回收,需要所有的线程都暂停下来,这个暂停的时间我们成为 Stop The World。
而为了实现 STW 这个操作, JVM 需要为每个线程选择一个点停止运行,这个点就叫做安全点(Safepoints)。
扩展阅读:JEP 376:ZGC 并发线程堆栈处理
5. JEP 380:Unix 域套接字通道
添加 UnixDomainSocketAddress.java 类用于支持 Unix 域套接字通道。
添加 Unix-domain socket 到 SocketChannel 和 ServerSocketChannel API 中。
添加枚举信息 java.net.StandardProtocolFamily.UNIX。
6. JEP 386:移植 Alpine Linux
Apine Linux 是一个独立的、非商业的 Linux 发行版,它十分的小,一个容器需要不超过 8MB 的空间,最小安装到磁盘只需要大约 130MB 存储空间,并且十分的简单,同时兼顾了安全性。
此提案将 JDK 移植到了 Apline Linux,由于 Apline Linux 是基于 musl lib 的轻量级 Linux 发行版,因此其他 x64 和 AArch64 架构上使用 musl lib 的 Linux 发行版也适用。
7. JEP 387:更好的 Metaspace
自从引入了 Metaspace 以来,根据反馈,Metaspace 经常占用过多的堆外内存,从而导致内存浪费,现在可以更及时地将未使用的 HotSpot class-metaspace 内存返还给操作系统,从而减少 Metaspace 的占用空间,并优化了 Metaspace 代码以降低后续的维护成本。
8. JEP 388:移植 Windows/AArch64
将 JDK 移植到 Windows/AArch64 架构上,Windows/AArch64 已经是终端用户市场的热门需求。
9. JEP 389:外部连接器 API(孵化)
这项提案让 Java 代码可以调用由其他语言(比如 C ,C++)编写的编译后的机器代码,替换了之前的 JNI 形式。
不过这还是一个孵化中的功能,运行时需要添加 --add-modules  jdk.incubator.foreign 参数来编译和运行 Java 代码。
下面是一个调用 C 语言函数方法,然后输出运行结果的例子。
- 编写一个 C 函数打印一个 "hello www.wdbyte.com"。
#include <stdio.h>
void printHello(){
	printf("hello www.wdbyte.com\n");
}
- 将上面的代码编译,然后输出到共享库 hello.so
$ gcc -c -fPIC hello.c
$ gcc -shared -o hello.so hello.o
$ ll
total 128
-rw-r--r--  1 darcy  staff    76B 10 28 19:46 hello.c
-rw-r--r--  1 darcy  staff   776B 10 28 19:46 hello.o
-rwxr-xr-x  1 darcy  staff    48K 10 28 19:47 hello.so
- 编写一个 Java 代码,调用 hello.so 的 printHello 方法。
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.LibraryLookup;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.util.Optional;
public class JEP389 {
  public static void main(String[] args) throws Throwable {
      Path path = Path.of("/Users/darcy/git/java-core/java-16/src/com/wdbyte/hello.so");
      LibraryLookup libraryLookup = LibraryLookup.ofPath(path);
      Optional<LibraryLookup.Symbol> optionalSymbol = libraryLookup.lookup("printHello");
      if (optionalSymbol.isPresent()) {
          LibraryLookup.Symbol symbol = optionalSymbol.get();
          FunctionDescriptor functionDescriptor = FunctionDescriptor.ofVoid();
          MethodType methodType = MethodType.methodType(Void.TYPE);
          MethodHandle methodHandle = CLinker.getInstance().downcallHandle(
                  symbol.address(),
                  methodType,
                  functionDescriptor);
          methodHandle.invokeExact();
      }
  }
}
- Java 代码编译。
$ javac --add-modules jdk.incubator.foreign JEP389.java
警告: 使用 incubating 模块: jdk.incubator.foreign
1 个警告
- Java 代码执行。
$ java --add-modules  jdk.incubator.foreign -Dforeign.restricted=permit JEP389.java
WARNING: Using incubator modules: jdk.incubator.foreign
警告: 使用 incubating 模块: jdk.incubator.foreign
1 个警告
hello www.wdbyte.com
扩展阅读: JEP 389:外部链接器 API(孵化器)
10. JEP 390:基于值的类的警告
添加了一个注解,用于标识当前是是基于值的类,比如 Java 8 引入的预防空指针的 Optional 类,现在已经添加了注解标识。
@jdk.internal.ValueBased
public final class Optional<T> {
    // ...
}
扩展阅读:基于值的类的警告
11. JEP 392:打包工具
在 Java 14 中,JEP 343 引入了打包工具,命令是 jpackage,在 Java 14 新功能文章里也做了介绍:
使用
jpackage命令可以把 JAR 包打包成不同操作系统支持的软件格式。jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main常见平台格式如下:
- Linux:
debandrpm- macOS:
pkganddmg- Windows:
msiandexe要注意的是,
jpackage不支持交叉编译,也就是说在 windows 平台上是不能打包成 macOS 或者 Linux 系统的软件格式的。
在 Java 15 中,继续孵化,现在在 Java 16 中,终于成为了正式功能。
下面是一个例子,把一个简单的 Java Swing 程序打包成当前操作系统支持的软件格式,然后安装到当前电脑。
编写 Java 代码
import javax.swing.*;
import java.awt.*;
public class JEP392 {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Hello World Java Swing");
        frame.setMinimumSize(new Dimension(800, 600));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JLabel lblText = new JLabel("Hello World!", SwingConstants.CENTER);
        frame.getContentPane().add(lblText);
        frame.pack();
        frame.setVisible(true);
    }
}
编译后,创建一个 JAR 文件。
$ javac JEP392.java
$ java JEP392.java
$ jar cvf JEP392.jar JEP392.class
将生成的 JEP392.jar 打包到符合当前平台的软件包中。
$ ~/develop/jdk-16.0.1.jdk/Contents/Home/bin/jpackage -i . -n JEP392 --main-jar hello.jar --main-class JEP392
$ ll
-rw-r--r--@ 1 darcy  staff    50M 10 28 20:34 JEP392-1.0.dmg
-rw-r--r--  1 darcy  staff   864B 10 28 20:22 JEP392.class
-rw-r--r--  1 darcy  staff   1.0K 10 28 20:30 JEP392.jar
-rw-r--r--  1 darcy  staff   588B 10 28 20:22 JEP392.java
ll 后显示的 JEP392-1.0.dmg(我用的 MacOS ,所以格式是 dmg)就是打包后的结果。
双击这个文件后可以像 mac 软件一样安装。其他平台类似。

安装后可以在启动台启动。

不同的系统安装位置不同:
- Linux: /opt
- MacOS : /Applications
- Windows:  C:\Program Files\
扩展阅读:JEP 392:打包工具
12. JEP 393:外部内存访问(第三次孵化)
此提案旨在引入新的 API 以允许 Java 程序安全有效的访问 Java 堆之外的内存。相关提案早在 Java 14 的时候就已经提出了,在 Java 15 中重新孵化,现在在 Java 16 中再次孵化。
此提案的目标如下:
- 通用:单个 API 应该能够对各种外部内存(如本机内存、持久内存、堆内存等)进行操作。
- 安全:无论操作何种内存,API 都不应该破坏 JVM 的安全性。
- 控制:可以自由的选择如何释放内存(显式、隐式等)。
- 可用:如果需要访问外部内存,API 应该是 sun.misc.Unsafa.
扩展阅读:外部内存访问
13. JEP 394:instanceof 模式匹配
改进 instanceof 在 Java 14 中已经提出,在 Java 15 中继续预览,而现在,在 Java 16 中成为正式功能。
在之前,使用 instanceof 需要如下操作:
if (obj instanceof String) {
    String s = (String) obj;    // grr...
    ...
}
多余的类型强制转换,而现在:
if (obj instanceof String s) {
    // Let pattern matching do the work!
    ...
}
14. JEP 395:Records
Record 成为 Java 16 的正式功能,下面是介绍 Java 14 时关于 Record 的介绍。
record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是 final 修饰,它会自动编译出 public get hashcode 、equals、toString 等方法,减少了代码编写量。
示例:编写一个 Dog record 类,定义 name 和 age 属性。
package com.wdbyte;
public record Dog(String name, Integer age) {
}
Record 的使用。
package com.wdbyte;
public class Java14Record {
    public static void main(String[] args) {
        Dog dog1 = new Dog("牧羊犬", 1);
        Dog dog2 = new Dog("田园犬", 2);
        Dog dog3 = new Dog("哈士奇", 3);
        System.out.println(dog1);
        System.out.println(dog2);
        System.out.println(dog3);
    }
}
输出结果:
Dog[name=牧羊犬, age=1]
Dog[name=田园犬, age=2]
Dog[name=哈士奇, age=3]
这个功能在 Java 15 中进行二次预览,在 Java 16 中正式发布。
15. JEP 396:默认强封装JDK内部
Java 9 JEP 261引入了 --illegal-access 控制内部 API 访问和 JDK 打包的选项。
此 JEP 将 --illegal-access 选项的默认模式从允许更改为拒绝。通过此更改,JDK的内部包和 API(关键内部 API除外)将不再默认打开。
该 JEP 的动机是阻止第三方库、框架和工具使用 JDK 的内部 API 和包,增加了安全性。
16. JEP 397:Sealed Classes(密封类)预览
Sealed Classes 再次预览,在 Java 15 新特性介绍文章里已经介绍过相关功能,并且给出了详细的使用演示,这里不再重复介绍。
下面是一段引用:
我们都知道,在 Java 中如果想让一个类不能被继承和修改,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。
而且 sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 final 、sealed 、non-sealed 三者之一。
扩展阅读:Java 15 新特性介绍
参考
- https://openjdk.java.net/projects/jdk/16/
- https://docs.oracle.com/en/java/javase/16/