Java 12八大新特性:耀眼的垃圾收集器

JDK 12新增功能中以Shenandoah垃圾收集器最为耀眼,它实现了暂停时间与Java堆大小无关,增强后的Switch表达式更实用,微基准测试工具Microbenchmark又添新用例。

以往Java版本普及度来看,Java 8依然独领风骚,但并不妨碍Oracle加速推进Java新版本。

低暂停的垃圾收集器-Shenandoah

相比以往以低暂停时间为目标的垃圾收集器,jdk 12提供的全新低暂停垃圾收集器-Shenandoah,使用了更大胆的实现方式,jvm垃圾回收期间对象的转移,与java应用程序并行,Shenandoah垃圾收集器暂停时间与堆的大小无关,无论是200 MB的堆空间,还是200 GB大小的堆,暂停时间都将保持一致。

就像那句备受争议的“软件工程中没有银弹(No Silver Bullet)”的说法一样,Shenandoah垃圾收集器并非适用于所有的场景,如果优先考虑吞吐量或内存占用,Shenandoah垃圾收集器并非理想选择。Shenandoah垃圾收集器适用于对响应性有要求,和可预测停顿时间的应用程序。Shenandoah垃圾收集器也不能解决,所有关于JVM暂停的问题。因为除了安全时间点(TTSP)之外的其他因素引起的暂停,已超出了JEP的范围。

Shenandoah使用并行的cpu周期和空间来改善暂停时间。Shenandoah通过增加一个指向Java对象的间接指针,使得GC线程能够在Java线程运行时进行堆压缩,标记和压缩为并发执行,因此,只需在对线程的栈进行扫描期间,暂停Java线程一段时间,以完成查找并更新对象图的根节点。

当然对于GC算法的选择并不仅限于最新版本的jdk,如Azul Systems的Zing JVM,以及早前jdk版本中发布的,基于彩色指针的低暂停收集器ZGC,并行和并发共存的G1垃圾收集器(G1中对象的转移过程(evacuation)不是并发的),以及使用度极高的CMS并发标记垃圾收集器(暂停时执行年轻代复制,并且从不压缩年老代)。

微基准测试工具Microbenchmark

Microbenchmark作为常规性能测试的一部分,在JDK源代码中添加一组基础的微基准测试,兼容已有的微基准测试(基于[Java Microbenchmark Harness (JMH)]),也可以创建新的基准测试,在Microbenchmark套件中包含大约一百个初始基准集。

Switch表达式扩展

扩展后的switch语句,可以使用语句或表达式,并且“传统”或“简化”两种都可以使用。这些变化将简化日常编码,在预览版中还提供了模式匹配(JEP 305)。
switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY                -> System.out.println(7);
    case THURSDAY, SATURDAY     -> System.out.println(8);
    case WEDNESDAY              -> System.out.println(9);
}

JVM常量API(JVM Constants API)

JVM依赖于一个常量池来确定类布局、实例、接口和数组。每个类都有一个常量池。当前使用Java数据类型表示这些可加载的常量时,在操作时效率并不高。JEP中的JVM常量API是为Java提供操纵类和方法提供了最好的方式。

首先,什么是可加载的常量?

每个Java类都有一个称为常量池表的东西。表中的每个条目都是可加载的常量,格式如下:

cp_info {
    u1 tag;
    u1 info[];
}

tag指定常量的类型,例如,7表示类,10表示方法引用,8表示字符串。info数组提供关于常量的更多信息。

JVM指令,例如ldc和invokedynamic指令,依赖于这个表中的信息(而不是类或接口的run-time layout)。当这些指令被执行时,可加载的常量变成一个活动值(live value),如:类、字符串、int等。

处理类文件的程序必须对这些字节码指令进行建模。如果常量类型类似于字符串或整数,则问题不大。如果常量类型是一个类,那问题就会变得复杂。加载类并不总是那么简单,有几种情况可能会导致失败,例如,类不存在或无法访问该类。

在Java 12中提供了一个API,可以象征性地处理这些值。例如,有一个名为ClassDesc的接口,可以用符号表示类的可加载常量,用MethodHandleDesc表示方法操作常量,用MethodTypeDesc表示方法类型常量,等等。

比如LambdaMetaFactory,新特性为invokedynamic调用点提供了引导方法。新的API将意味着这个类的工作方式可以大大简化。

保留AArch64端口

删除JDK中已有的两个64位ARM端口中的一个(src/hotspot/cpu/arm和open/src/hotspot/cpu/aarch64,两者都是aarch64接口/标准的实现),只保留32位ARM端口和64位aarch64端口,意在消除维护两个端口所做的重复工作,让贡献者将他们的精力集中在单个64位ARM实现上。

默认CDS

在64位平台上使用默认类列表,增强了JDK生成的类共享(CDS),改善了开箱即用的启动时间,取消了用户必需运行-Xshare:dump,才能使用CDS的功能。

可中止的G1垃圾收集器

如果G1垃圾收集器有可能超过预期的暂停时间,则可以使用中止选项。

G1的目标之一是满足用户预期的暂停时间,G1使用高级分析引擎来选择收集期间要完成的工作量(这部分取决于应用程序行为)。它会选择一些区域(regions)做为回收的对象(collection set)。一旦确定了collection set就会开始收集工作,则G1必须在不停止的情况下,对collection set区域中的活动对象进行处理。如果选择了过大的collection set,则有可能导致G1超过预期的暂停时间,需要一种检测机制来发现,G1何时开始反复选择错误的工作量(收集区域选择过大),如果存在这种现象,则让G1逐步递增地执行收集工作,这种机制能够让G1在大多时间里,都能满足预期的暂停时间。

G1归还未使用的内存

增强后的G1垃圾收集器,可以在空闲时自动将Java堆内存返还给操作系统。如果应用程序活动非常低,G1应该在合理的时间段内释放未使用的Java堆内存。为了实现向操作系统返还最大内存量的目标,G1将在应用程序不活动期间,定期尝试触发并发周期以确定整体Java堆使用情况。这将导致它自动将Java堆的未使用部分返回给操作系统。

支持Unicode 11 

JDK 12版本增加了对Unicode 11.0.0的支持。JDK 12引入的Unicode 11.0.0以支持更多的字符串和表情符号。