Java 12 八大新特性:JVM历上最牛GC

JDK 12新增功能中以Shenandoah垃圾收集器最为耀眼,这款暂停时间与Java堆大小无关的GC,似乎错过了要在最美丽的年华遇见你,在这个轻量级分布式大行其道的年代,能否赢得掌声犹未可知。

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

低暂停垃圾收集器-Shenandoah

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

就像那句备受争议的“软件工程中没有银弹(No Silver Bullet)”的说法一样,Shenandoah垃圾收集器并非适用于所有场合,考虑吞吐量或内存占用的场景,Shenandoah垃圾收集器并非理想选择。

Shenandoah垃圾收集器适合对响应性有高要求,暂停时间可预测的应用程序。Shenandoah垃圾收集器并未解决,所有关于jvm暂停时间的问题。因为除了安全时间点(TTSP)之外,其他因素引起的暂停,已超出了JEP的范围。

Shenandoah通过增加一个指向Java对象的间接指针,让GC线程能够在Java程序运行期间,对堆空间进行压缩,标记和压缩为并发执行,因此,只需在对线程栈扫描期间,暂停Java线程即可,从而完成查找和更新对象图的工作。

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

微基准测试套件(Microbenchmark)

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

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);
}

可中止的G1垃圾收集器

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

G1的目标之一是满足用户预期的暂停时间,G1使用高级分析引擎,分析并预测能在指定时间内可完成的回收区域,一旦确定了collection set就会开始GC过程,在过去G1对collection set区域中的活动对象进行处理时不会中断。

如果选择了过大的collection set,就有可能导致G1超出预期的暂停时间,所以,需要一种检测机制来发现,G1何时开始反复选择错误的工作量(收集区域选择过大),若存在这种现象,则让G1逐步递增性地执行收集工作,这种机制让G1在大多时间里,都能达到预期的暂停时间。

G1归还未使用的内存

增强后的G1垃圾收集器,可在空闲时自动将Java堆内存返还给操作系统。如果应用程序活动性非常低,G1会在合理的时间段内,释放未使用的Java堆内存。

为了实现向操作系统返还尽可能多的内存,G1会在应用程序不活动期间,定期尝试触发GC并发周期,以确定Java堆整体使用情况,以达成自动返回未使用的Java堆。

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等。

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

在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的功能。

支持Unicode 11 

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