Java新特性梳理——Java12

概述

2019 年 3 月 19 日,Java 12 正式发布了,总共有 8 个新的 JEP(JDK Enhancement Proposals)。

Java 12 的版本特性地址:openjdk.java.net/projects/jd...

语法层次改变

switch表达式(Preview)

传统的 switch 声明语句(switch statement)在使用中有一些问题:

  • 匹配自上而下,若无 break, 后面的 case 语句都会执行。
  • 不同的 case 语句定义的变量名不能重复。
  • 不能在一个 case 里写多个执行结果一致的条件。
  • 整个 switch 不能作为表达式返回值。

Java 12 提供增强版的 switch 语句或称为 switch 表达式来写出更加简化的代码。

switch 表达式也是作为预览语言功能的第一个语言改动被引入新版 Java 中来的,这是一种引入新特性的测试版的方法。通过这种方式,能够根据用户反馈进行升级、更改。如果没有被很好的接纳,则可以完全删除该功能。预览功能的没有被包含在 Java SE 规范中。也就是说: 这不是一个正式的语法,是暂时进行测试的一种语法。

扩展的 switch 语句,不仅可以作为语句(statement),还可以作为表达式(expression),并且两种写法都可以,使用传统的 switch 语法,或者使用简化的 case L -> 模式匹配语法作用于不同范围并控制执行流。这些更改将简化日常编码工作,并为 switch 中的模式匹配(JEP 305)做好准备。

  • 使用 Java 12 中 switch 表达式的写法,省去了 break 语句,避免了因少写 break 而出错。
  • 同时将多个 case 合并到一行,显得简洁、清晰也更加优雅的表达逻辑分支,其具体写法就是将之前的 case 语句表成了:case L ->,即如果条件匹配 case ,则执行标签右侧的代码,同时标签右侧的代码段只能是表达式、代码块或 throw 语句。
  • 为了保持兼容性,case 条件语句中依然可以使用字符,这时 fall-through 规则依然有效的,即不能省略原有的 break 语句,但是同一个 switch 结构里不能混用 ->: ,否则会有编译错误。并且简化后的 switch 代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个 switch 结构,也不用根据不同的判断条件来给变量赋值。
java 复制代码
public static void main(String[] args) {
    Month month=Month.APRIL;
    String season;
    switch (month){
        case DECEMBER:
        case JANUARY:
        case FEBRUARY:
            season="冬";
            break;
        case MARCH:
        case APRIL:
        case MAY:
            season="春";
            break;
        case JUNE:
        case JULY:
        case AUGUST:
            season="夏";
            break;
        case SEPTEMBER:
        case OCTOBER:
        case NOVEMBER:
            season="秋";
            break;
        default:
            throw new RuntimeException("NoSuchMonthException");
        }
        System.out.println(season);
    }

使用 Java 12 新特性简化后:

java 复制代码
public static void main(String[] args) {
    Month month = Month.APRIL;
    String season;
    switch (month) {
        case DECEMBER, JANUARY, FEBRUARY -> season = "冬";
        case MARCH, APRIL, MAY -> season = "春";
        case JUNE, JULY, AUGUST -> season = "夏";
        case SEPTEMBER, OCTOBER, NOVEMBER -> season = "秋";
        default -> throw new RuntimeException("无效数据");
    }
    System.out.println(season);
}

API层次改变

支持数字压缩格式化

NumberFormat 添加了对以紧凑形式格式化数字的支持。紧凑数字格式是指以简短或人类可读形式表示的数字。例如,在 en_US 语言环境中,1000 可以格式化为 1K。1000000 可以格式化为 1M。具体取决于指定的样式NumberFormat.Style

java 复制代码
var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA, NumberFormat.Style.SHORT);
System.out.println(cnf.format(1_0000)); // 1万
System.out.println(cnf.format(1_9200)); // 2万
System.out.println(cnf.format(1_000_000)); // 100万
System.out.println(cnf.format(1L << 30)); // 11亿
System.out.println(cnf.format(1L << 40)); // 1万亿
System.out.println(cnf.format(1L << 50)); // 1126万亿

String新方法

String#transform(Function):它提供的函数作为输入提供给特定的 String 实例,并返回该函数返回的输出。

java 复制代码
public static void main(String[] args) {
    var result = "abc".transform(input -> input + "def");
    System.out.println(result); // abcdef

    result = "abc"
            .transform(input -> input + " def")
            .transform(String::toUpperCase);
    System.out.println(result); // ABCDEF
}

transform 源码如下:

java 复制代码
public <R> R transform(Function<? super String, ? extends R> f) {
    return f.apply(this);
}

String 中的 indent 方法:该方法允许我们调整 String 实例的缩进:

java 复制代码
public static void main(String[] args) {
    String result = "Java\nGolang\nPython".indent(3);
    System.out.println(result);
    //    Java 
    //    Golang
    //    Python
}

Files新增mismatch方法

mismatch 方法:对比两个文件的差异,返回从哪个字节开始出现了不一致:

java 复制代码
FileWriter fileWriter = new FileWriter("d:/a.txt");
fileWriter.write("a");
fileWriter.write("b");
fileWriter.write("c");
fileWriter.close();
FileWriter fileWriterB = new FileWriter("d:/b.txt");
fileWriterB.write("a");
fileWriterB.write("1");
fileWriterB.write("c");
fileWriterB.close();
System.out.println(Files.mismatch(Path.of("d:/a.txt"), Path.of("d:/b.txt")));

新的GC特性

Shenandoah GC

Shenandoah GC:低停顿时间的 GC(预览):Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针对 JVM 上的内存收回实现低停顿的需求。该设计将与应用程序线程并发,通过交换 CPU 并发周期和空间以改善停顿时间,使得垃圾回收器执行线程能够在 Java 线程运行时进行堆压缩,并且标记和整理能够同时进行,因此避免了在大多数 JVM 垃圾收集器中所遇到的问题。据 Red Hat 研发 Shenandoah 团队对外宣称:Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。与其他 Pauseless GC 类似,Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。这是一个实验性功能,不包含在默认(Oracle)的 OpenJDK 版本中。

垃圾回收器的任务是识别和回收垃圾对象进行内存清理。垃圾回收要求系统进入一个停顿的状态。停顿的目的是终止所有应用程序的执行,这样才不会有新的垃圾产生,同时保证了系统状态在某一个瞬间的一致性,并且有益于垃圾回收器更好地标记垃圾对象。停顿产生时整个应用程序会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW 。 如果 Stop-the-World 出现在新生代的 Minor GC 中时, 由于新生代的内存空间通常都比较小,所以暂停的时间较短,在可接受的范围内,老年代的 Full GC 时,程序的工作线程被暂停的时间将会更久。内存空间越大,执行Full GC 的时间就会越久,工作线程被暂停的时间也就会更长。到目前为止,哪怕是 G1 也不能完全避免 Stop-the-world 情况发生,只能说垃圾回收器越来越优秀,尽可能地缩短了暂停时间。

Shenandoah工作原理:

从原理的角度,我们可以参考该项目官方的示意图,其内存结构与 G1 非常相似,都是将内存划分为类似棋盘的 region。整体流程与 G1 也是比较相似的,最大的区别在于实现了并发的 疏散(Evacuation) 环节,引入的 Brooks Forwarding Pointer 技术使得 GC 在移动对象时,对象引用仍然可以访问。

G1 内存设计:

Shenandoah GC 工作周期如下所示:

  1. Init Mark 启动并发标记阶段。
  2. 并发标记遍历堆阶段。
  3. 并发标记完成阶段。
  4. 并发整理回收无活动区域阶段。
  5. 并发 Evacuation 整理内存区域阶段。
  6. Init Update Refs 更新引用初始化阶段。
  7. 并发更新引用阶段。
  8. Final Update Refs 完成引用更新阶段。
  9. 并发回收无引用区域阶段。

可中断的G1 Mixed GC

当 G1 垃圾回收器的回收超过暂停时间的目标,则能中止垃圾回收过程。

G1 是一个垃圾收集器,设计用于具有大量内存的多处理器机器。由于它提高了性能效率,G1 垃圾收集器最终将取代 CMS 垃圾收集器。该垃圾收集器设计的主要目标之一是满足用户设置的预期的 JVM 停顿时间。

G1 采用一个高级分析引擎来选择在收集期间要处理的工作量,此选择过程的结果是一组称为 GC 回收集 collection set(CSet)的区域。一旦收集器确定了 GC 回收集 并且 GC 回收、整理工作已经开始,这个过程是 without stopping 的,即 G1 收集器必须完成收集集合的所有区域中的所有活动对象之后才能停止;但是如果收集器选择过大的 GC 回收集,此时的 STW 时间会过长超出目标 pause time。

这种情况在 mixed collections 时候比较明显。这个特性启动了一个机制,当选择了一个比较大的 collection set,Java 12 中将把 GC 回收集(混合收集集合)拆分为 mandatory(必需或强制)及 optional 两部分(当完成mandatory 的部分,如果还有剩余时间则会去处理 optional 部分)来将 mixed collections 从 without stopping 变为 abortable,以更好满足指定 pause time 的目标。

  • 其中必须处理的部分包括 G1 垃圾收集器不能递增处理的 GC 回收集的部分(如:年轻代),同时也可以包含老年代以提高处理效率。
  • 将 GC 回收集拆分为必需和可选部分时,垃圾收集过程优先处理必需部分。同时,需要为可选 GC 回收集部分维护一些其他数据,这会产生轻微的 CPU 开销,但小于 1% 的变化,同时在 G1 回收器处理 GC 回收集期间,本机内存使用率也可能会增加,使用上述情况只适用于包含可选 GC 回收部分的 GC 混合回收集合。
  • 在 G1 垃圾回收器完成收集需要必需回收的部分之后,如果还有时间的话,便开始收集可选的部分。但是粗粒度的处理,可选部分的处理粒度取决于剩余的时间,一次只能处理可选部分的一个子集区域。在完成可选收集部分的收集后,G1 垃圾回收器可以根据剩余时间决定是否停止收集。如果在处理完必需处理的部分后,剩余时间不足,总时间花销接近预期时间,G1 垃圾回收器也可以中止可选部分的回收以达到满足预期停顿时间的目标。

增强的G1

上面介绍了 Java 12 中增强了 G1 垃圾收集器关于混合收集集合的处理策略,这节主要介绍在 Java 12 中同时也对 G1 垃圾回收器进行了改进,使其能够在空闲时自动将 Java 堆内存返还给操作系统,这也是 Java 12 中的另外一项重大改进。

目前 Java 11 版本中包含的 G1 垃圾收集器暂时无法及时将已提交的 Java 堆内存返回给操作系统。为什么呢? G1目前只有在 Full GC 或者 concurrent cycle(并发处理周期)的时候才会归还内存,由于这两个场景都是 G1 极力避免的,因此在大多数场景下可能不会及时归还 committed Java heap memory 给操作系统。除非有外部强制执行。

在使用云平台的容器环境中,这种不利之处特别明显。即使在虚拟机不活动,但如果仍然使用其分配的内存资源,哪怕是其中的一小部分,G1 回收器也仍将保留所有已分配的 Java 堆内存。而这将导致用户需要始终为所有资源付费, 哪怕是实际并未用到,而云提供商也无法充分利用其硬件。如果在此期间虚拟机能够检测到 Java 堆内存的实际使用情况,并在利用空闲时间自动将 Java 堆内存返还,则两者都将受益。

为了尽可能的向操作系统返回空闲内存,G1 垃圾收集器将在应用程序不活动期间定期生成或持续循环检查整体 Java堆使用情况,以便 G1 垃圾收集器能够更及时的将 Java 堆中不使用内存部分返还给操作系统。**对于长时间处于空闲状态的应用程序,此项改进将使 JVM 的内存利用率更加高效。而在用户控制下,可以可选地执行Full GC,以使返回的内存量最大化。

Java 12 的这个特性新增了两个参数分别是 G1 PeriodicGCInterval 及 G1 PeriodicGCSystemLoadThreshold,设置为 0 的话,表示禁用。如果应用程序为非活动状态,在下面两种情况任何一个描述下,G1 回收器会触发定期垃圾收集:

自上次垃圾回收完成以来已超过 G1PeriodicGCInterval(milliseconds),并且此时没有正在进行的垃圾回收任务。如果 G1PeriodicGCInterval 值为零表示禁用快速回收内存的定期垃圾收集。

  • 应用所在主机系统上执行方法 getloadavg(),默认一分钟内系统返回的平均负载值低于 G1PeriodicGCSystemLoadThreshold 指定的阈值,则触发 Full GC 或者 concurrent GC(如果开启 G1PeriodicGCInvokesConcurrent),GC 之后 Java heap size 会被重写调整,然后多余的内存将会归还给操作系统。如果 G1PeriodicGCSystemLoadThreshold 值为零,则此条件不生效。

如果不满足上述条件中的任何一个,则取消当期的定期垃圾回收。等一个 G1PeriodicGCInterval 时间周期后,将重新考虑是否执行定期垃圾回收。

G1 定期垃圾收集的类型根据 G1PeriodicGCInvokesConcurrent 参数的值确定:如果设置值了,G1 垃圾回收器将继续上一个或者启动一个新并发周期;如果没有设置值,则 G1 回收器将执行一个 Full GC。在每次一次 GC 回收末尾,G1 回收器将调整当前的 Java 堆大小,此时便有可能会将未使用内存返还给操作系统。新的 Java 堆内存大小根据现有配置确定,具体包括下列配置:-XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio-Xms-Xmx

默认情况下,G1 回收器在定期垃圾回收期间新启动或继续上一轮并发周期,将最大限度地减少应用程序的中断。如果定期垃圾收集严重影响程序执行,则需要考虑整个系统 CPU 负载,或让用户禁用定期垃圾收集。

其他特性

JVM常量API

Java 12 中引入 JVM 常量 API,用来更容易地对关键类文件(Key Class File) 和运行时构件(Artifact)的名义描述(Nominal Description)进行建模,特别是对那些从常量池加载的常量,这是一项非常技术性的变化,能够以更简单、标准的方式处理可加载常量。

具体来说就是 java.base 模块新增了 java.lang.constant 包。包中定义了一系列基于值的符号引用(JVMS 5.1)类型,它们能够描述每种可加载常量。

引入了 ConstantDesc 接口(ClassDescMethodTypeDescMethodHandleDesc 这几个接口直接继承了ConstantDesc 接口)以及 Constable 接口。ConstantDesc 接口定义了 resolveConstantDesc 方法,Constable 接口定义了 describeConstable 方法。StringIntegerLongFloatDouble 均实现了这两个接口,而 EnumDesc 实现了 ConstantDesc 接口。

符号引用以纯 nominal 形式描述可加载常量,与类加载或可访问性上下文区分开。有些类可以作为自己的符号引用(例如 String)。而对于可链接常量,另外定义了一系列符号引用类型,具体包括:ClassDesc(Class 的可加载常量标称描述符),MethodTypeDesc(方法类型常量标称描述符),MethodHandleDesc(方法句柄常量标称描述符)和DynamicConstantDesc(动态常量标称描述符),它们包含描述这些常量的 nominal 信息。此 API 对于操作类和方法的工具很有帮助。

微机准测试套件

JMH,即 Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件。何谓 Micro Benchmark 呢?简单的来说就是基于方法层面的基准测试,精度可以达到微秒级。当你定位到热点方法,希望进一步优化方法性能的时候,就可以使用 JMH 对优化的结果进行量化的分析。

要使用 JMH,首先需要准备好 Maven 环境,如果要在现有 Maven 项目中使用 JMH,只需要把生成出来的两个依赖以及 shade 插件拷贝到项目的 pom 中即可。

xml 复制代码
<dependencies>
    <dependency>
	<groupId>org.openjdk.jmh</groupId>
	<artifactId>jmh-core</artifactId>
	<version>0.7.1</version>
    </dependency>

    <dependency>
        <groupId>org.openjdk.jmh</groupId>
        <artifactId>jmh-generator-annprocess</artifactId>
        <version>0.7.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>microbenchmarks</finalName>
                <transformers>
                     <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.openjdk.jmh.Main</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

只保留一个AArch64实现

当前 Java 11 及之前版本JDK中存在两个 64 位 ARM 端口。这些文件的主要来源位于 src/hotspot/cpu/arm 和 open/src/hotspot/cpu/aarch64 目录中。尽管两个端口都产生了 aarch64 实现,我们将前者(由 Oracle 贡献)称为 arm64 ,将后者称为 aarch64。

Java 12 中将删除由 Oracle 提供的 arm64 端口相关的所有源码,即删除目录 open/src/hotspot/cpu/arm 中关于 64-bit 的这套实现,只保留其中有关 32-bit ARM端口的实现,余下目录的 open/src/hotspot/cpu/aarch64 代码部分就成了 AArch64 的默认实现。

默认生成类的数据共享(CDS)归档文件

我们知道在同一个物理/虚拟机上启动多个 JVM 时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。所以 Java 团队引入了类数据共享机制(Class Data Sharing,简称 CDS) 的概念,通过把一些核心类在每个 JVM 间共享,每个 JVM 只需要装载自己的应用类即可。好处是:启动时间减少了,另外核心类是共享的,所以 JVM 的内存占用也减少了。

Java 12 针对 64 位平台下的 JDK 构建过程进行了增强改进,使其默认生成类数据共享(CDS)归档,以进一步达到改进应用程序的启动时间的目的,同时也避免了需要手动运行:java -Xshare:dump 的需要,修改后的 JDK 将在${JAVA_HOME}/lib/server 目录中生成一份名为 classes.jsa 的默认 archive 文件(大概有18M)方便大家使用。

当然如果需要,也可以添加其他 GC 参数,来调整堆大小等,以获得更优的内存分布情况,同时用户也可以像之前一样创建自定义的 CDS 存档文件。

支持Unicode 11

JDK 12 版本包括对 Unicode 11.0.0 的支持。在发布支持 Unicode 10.0.0 的 JDK 11 之后,Unicode 11.0.0 引 入了以下 JDK 12 中包含的新功能:684 new characters、11 new blocks、7 new scripts。

其他新增

  • Collectors 新增 teeing 方法用于聚合两个 downstream 的结果。
  • CompletionStage 新增 exceptionallyAsyncexceptionallyComposeAsync 方法,允许方法体在异步线程执行,同时新增了 exceptionallyCompose 方法支持在 exceptionally 的时候构建新的 CompletionStage
  • ZGC: Concurrent Class Unloading:
    • ZGC 在 Java 11 的时候还不支持 class unloading,Java 12 对 ZGC 支持了 Concurrent Class Unloading,默认是开启,使用 -XX:-ClassUnloading 可以禁用。
  • 新增 -XX:+ExtensiveErrorReports
    • -XX:+ExtensiveErrorReports 可以用于在 jvm crash 的时候收集更多的报告信息到 hs_err.log 文件中,product builds 中默认是关闭的,要开启的话,需要自己添加 -XX:+ExtensiveErrorReports 参数
  • 新增安全相关的改进:
    • 支持 java.security.manager 系统属性,当设置为 disallow 的时候,则不使用 SecurityManager 以提升性能,如果此时调用 System.setSecurityManager 则会抛出 UnsupportedOperationExceptionkeytool 新增 -groupname 选项允许在生成 key pair 的时候指定一个 named group 新增 PKCS12 KeyStore 配置属性用于自定义 PKCS12 keystores 的生成 Java Flight Recorder 新增了 security-related 的 event 支持ChaCha20 and Poly1305 TLS Cipher Suites。

移除项

  • 移除 com.sun.awt.SecurityWarnin
  • 移除 FileInputStreamFileOutputStreamJava.util.ZipFile/Inflator/Deflatorfinalize 方法
  • 移除 GTE CyberTrust Global Root
  • 移除 javac 的 -source-target 对 6 及 1.6 的支持,同时移除 --release 选项;
相关推荐
神仙别闹20 分钟前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭44 分钟前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫1 小时前
泛型(2)
java
超爱吃士力架1 小时前
邀请逻辑
java·linux·后端
南宫生1 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石1 小时前
12/21java基础
java
李小白661 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp2 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶2 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗2 小时前
常用类晨考day15
java