Java 12 新特性概述(一)

Java 12 已如期于 3 月 19 日正式发布,此次更新是 Java 11 这一长期支持版本发布之后的一次常规更新,截至目前,Java 半年为发布周期,并且不会跳票承诺的发布模式,已经成功运行一年多了。通过这样的方式,Java 开发团队能够将一些重要特性尽早的合并到 Java Release 版本中,以便快速得到开发者的反馈,避免出现类似 Java 9 发布时的两次延期的情况。

Java 12 早在 2018 年 12 月便进入了 Rampdown Phase One 阶段,这意味着该版本所有新的功能特性被冻结,不会再加入更多的 JEP。该阶段将持续大概一个月,主要修复 P1-P3 级错误。主要时间节点如下:

2018-12-13 Rampdown 第一阶段 ( 从主线分离 )
2019-01-17 Rampdown 第二阶段
2019-02-07 发布候选阶段
2019-03-19 正式发布

本文主要针对 Java 12 中的新特性展开介绍,让您快速了解 Java 12 带来的变化。

Shenandoah:一个低停顿垃圾收集器(实验阶段)

Java 12 中引入一个新的垃圾收集器:Shenandoah,它是作为一中低停顿时间的垃圾收集器而引入到 Java 12 中的,其工作原理是通过与 Java 应用程序中的执行线程同时运行,用以执行其垃圾收集、内存回收任务,通过这种运行方式,给虚拟机带来短暂的停顿时间。

Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目,旨在针对 JVM 上的内存收回实现低停顿的需求。该设计将与应用程序线程并发,通过交换 CPU 并发周期和空间以改善停顿时间,使得垃圾回收器执行线程能够在 Java 线程运行时进行堆压缩,并且标记和整理能够同时进行,因此避免了在大多数 JVM 垃圾收集器中所遇到的问题。

据 Red Hat 研发 Shenandoah 团队对外宣称,Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。


Shenandoah GC 工作周期如下所示

上图对应工作周期如下:

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

需要了解不是唯有 GC 停顿可能导致常规应用程序响应时间比较长。具有较长的 GC 停顿时间会导致系统响应慢的问题,但响应时间慢并非一定是 GC 停顿时间长导致的,队列延迟、网络延迟、其他依赖服务延迟和操作提供调度程序抖动等都可能导致响应变慢。使用 Shenandoah 时需要全面了解系统运行情况,综合分析系统响应时间。各种 GC 工作负载对比如下所示:

图 2. 各种 GC 工作负载对比

下面推荐几个配置或调试 Shenandoah 的 JVM 参数:

  • -XX:+AlwaysPreTouch:使用所有可用的内存分页,减少系统运行停顿,为避免运行时性能损失。
  • -Xmx == -Xmsv:设置初始堆大小与最大值一致,可以减轻伸缩堆大小带来的压力,与 AlwaysPreTouch 参数配合使用,在启动时提交所有内存,避免在最终使用中出现系统停顿。
  • -XX:+ UseTransparentHugePages:能够大大提高大堆的性能,同时建议在 Linux 上使用时将 /sys/kernel/mm/transparent_hugepage/enabled 和 /sys/kernel/mm/transparent_hugepage/defragv 设置为:madvise,同时与 AlwaysPreTouch 一起使用时,init 和 shutdownv 速度会更快,因为它将使用更大的页面进行预处理。
  • -XX:+UseNUMA:虽然 Shenandoah 尚未明确支持 NUMA(Non-Uniform Memory Access),但最好启用此功能以在多插槽主机上启用 NUMA 交错。与 AlwaysPreTouch 相结合,它提供了比默认配置更好的性能。
  • -XX:+DisableExplicitGC:忽略代码中的 System.gc() 调用。当用户在代码中调用 System.gc() 时会强制 Shenandoah 执行 STW Full GC ,应禁用它以防止执行此操作,另外还可以使用 -XX:+ExplicitGCInvokesConcurrent,在 调用 System.gc() 时执行 CMS GC 而不是 Full GC,建议在有 System.gc() 调用的情况下使用。

不过目前 Shenandoah 垃圾回收器还被标记为实验项目,需要使用参数:- XX:+UnlockExperimentalVMOptions 启用。更多有关如何配置、调试 Shenandoah 的信息,请参阅 henandoah wiki。

增加一套微基准套件

Java 12 中添加一套新的基本的微基准测试套件,该套微基准测试套件基于 JMH(Java Microbenchmark Harness),使开发人员可以轻松运行现有的微基准测试并创建新的基准测试,其目标在于提供一个稳定且优化过的基准,其中包括将近 100 个基准测试的初始集合,并且能够轻松添加新基准、更新基准测试和提高查找已有基准测试的便利性。

微基准套件与 JDK 源代码位于同一个目录中,并且在构建后将生成单个 Jar 文件。但它是一个单独的项目,在支持构建期间不会执行,以方便开发人员和其他对构建微基准套件不感兴趣的人在构建时花费比较少的构建时间。

要构建微基准套件,用户需要运行命令:make build-microbenchmark,类似的命令还有:make test TEST=”micro:java.lang.invoke” 将使用默认设置运行 java.lang.invoke 相关的微基准测试。关于配置本地环境可以参照文档 docs/testing.md|html。

Switch 表达式扩展(预览功能)

Java 11 以及之前 Java 版本中的 Switch 语句是按照类似 C、C++ 这样的语言来设计的,在默认情况下支持 fall-through 语法。虽然这种传统的控制流通常用于编写低级代码,但 Switch 控制语句通常运用在高级别语言环境下的,因此其容易出错性掩盖其灵活性。

在 Java 12 中重新拓展了 Switch 让它具备了新的能力,通过扩展现有的 Switch 语句,可将其作为增强版的 Switch 语句或称为 “Switch 表达式”来写出更加简化的代码。

Switch 表达式也是作为预览语言功能的第一个语言改动被引入新版 Java 中来的,预览语言功能的想法是在 2018 年初被引入 Java 中的,本质上讲,这是一种引入新特性的测试版的方法。通过这种方式,能够根据用户反馈进行升级、更改,在极端情况下,如果没有被很好的接纳,则可以完全删除该功能。预览功能的关键在于它们没有被包含在 Java SE 规范中。

在 Java 11 以及之前版本中传统形式的 Switch 语句写法如下:

清单 1. Switch 语句示例

int dayNumber;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        dayNumber = 6;
        break;
    case TUESDAY:
        dayNumber = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        dayNumber = 8;
        break;
    case WEDNESDAY:
        dayNumber = 9;
        break;
    default:
        throw new IllegalStateException("Huh? " + day);
}

上面代码中多处出现 break 语句,显得代码比较冗余,同时如果某处漏写一段 break 语句,将导致程序一直向下穿透执行的逻辑错误,出现异常结果,同时这种写法比较繁琐,也容易出问题。

换做 Java 12 中的 Switch 表达式,上述语句写法如下:
清单 2. Switch 表达式示例

int dayNumber = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
    default                      -> throw new IllegalStateException("Huh? " + day);
}

使用 Java 12 中 Switch 表达式的写法,省去了 break 语句,避免了因少些 break 而出错,同时将多个 case 合并到一行,显得简洁、清晰也更加优雅的表达逻辑分支,其具体写法就是将之前的 case 语句表成了:case L ->,即如果条件匹配 case L,则执行 标签右侧的代码 ,同时标签右侧的代码段只能是表达式、代码块或 throw 语句。为了保持兼容性,case 条件语句中依然可以使用字符 : ,这时 fall-through 规则依然有效的,即不能省略原有的 break 语句,但是同一个 Switch 结构里不能混用 -> 和 : ,否则会有编译错误。并且简化后的 Switch 代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个 Switch 结构,也不用根据不同的判断条件来给变量赋值。

Java 11 以及之前版本中,Switch 表达式支持下面类型: byte、char、short、int、Byte、Character、Short、Integer、enum、tring,在未来的某个 Java 版本有可能会允许支持 float、double 和 long (以及上面类型的封装类型)。

Java 12 新特性概述(二)

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java