阿里西溪B区一角 By 程序猿阿朗
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 代码中使用的具体说明。
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 哈希之间进行转换。
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)。
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
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:
deb
andrpm
- macOS:
pkg
anddmg
- Windows:
msi
andexe
要注意的是,
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 软件一样安装。其他平台类似。
安装Java软件
安装后可以在启动台启动。
启动测试
不同的系统安装位置不同:
- Linux:
/opt
- MacOS :
/Applications
- Windows:
C:\Program Files\
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
三者之一。