vlambda博客
学习文章列表

重新复习一下JDK14的9大重磅特性


JDK14特性一览:

  • JEP 305: Pattern Matching for instanceof (Preview)
  • JEP 358: Helpful NullPointerExceptions
  • JEP 361: Switch Expressions (Standard)
  • JEP 345: NUMA-Aware Memory Allocation for G1
  • JEP 349: JFR Event Streaming
  • JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination
  • JEP 363: Remove the CMS Garbage Collector
  • JEP 364: ZGC on macOS
  • JEP 368: Text Blocks (Second Preview)

JEP 305: Pattern Matching for instanceof (Preview)

很明显这个特性跟使用instanceof有关。平常我们写代码是这样的。很明显这不是最优的方式,怎么看怎么别捏,代码显得有点冗余乏味,我们既要类型判断,还要类型强制转换:

if (obj instanceof String) {
String s = (String) obj;
// use s
}

那么新的方式是怎么样的呢?请往下看。厉不厉害,牛不牛逼:

if (obj instanceof String s) {
//todo can use s here
} else {
//todo can't use s here
}

而且还能用的更复杂一些,需要注意的是,下面这种写法时,必须是&&,而不能是||,为什么有这个限制,我想很容易理解吧:

if (obj instanceof String s && s.contains("afei")) {
... ...
}

JEP 358: Helpful NullPointerExceptions

这个特性有点意思,绝对非常有用。想象我们有一行这样的代码,并且在这里抛出了空指针,那么,我们没办法知道空指针是由于a引起的,还是a.b引起的,还是a.b.c引起的:

int index = a.b.c.i ;

所以,我们可能要将代码改造成这样,这样才能在代码抛出NPE时更容易定位问题:

if (a!=null){
if (a.b!=null){
if (a.b.c!=null){
int index = a.b.c.i ;
}
}
}

JEP358这个特性就是帮我们解决这个问题的。假设我们的代码还是这样写的:int index = a.b.c.i ,并且由于a.b为null引起的空指针,那么抛出的异常信息是这样的,这个异常就非常友好了吧:

Exception in thread "main" java.lang.NullPointerException:
Cannot read field "c" because "a.b" is null
at Prog.main(Prog.java:5)

数组方式也是一样的,假设有一行这样的代码:int height = a[i][j][k],并且由于a[i][j]为空导致的NPE,那么异常信息是这样的:

Exception in thread "main" java.lang.NullPointerException:
Cannot load from object array because "a[i][j]" is null
at Prog.main(Prog.java:5)

JEP 361: Switch Expressions (Standard)

这个特性也是继承自JDK13的JEP 354: Switch Expressions (Preview),有一段switch老语法代码如下:

switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}

这段代码显得有点冗余,新的语法代码如下,很明显简练很多:

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

而且,新的switch语法能直接将其作为表达式,用法如下:

int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};

新的switch语法相比以前灵活了很多很多!

JEP 345: NUMA-Aware Memory Allocation for G1

了解这个特性之前,我们需要了解什么是NUMA。NUMA就是非统一内存访问架构(英语:non-uniform memory access,简称NUMA),是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。在NUMA下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。如下图所示,Node0中的CPU如果访问Node0中的内存,那就是访问本地内存,如果它访问了Node1中的内存,那就是远程访问,性能较差:

重新复习一下JDK14的9大重磅特性

JEP345希望通过实现NUMA-aware的内存分配,改进G1在大型机上的性能!现代的multi-socket服务器越来越多都有NUMA,意思是,内存到每个socket的距离是不相等的,内存到不同的socket之间的访问是有性能差异的,这个距离越长,延迟就会越大,性能就会越差!(https://openjdk.java.net/jeps/345)。只需要设置JVM参数:+XX:+UseNUMA 后, 当JVM初始化的时候(即Java应用启动的时候),G1的Region集合就会被均匀的分散到所有有效的NUMA节点上。

JEP 349: JFR Event Streaming

Java为了更方便的了解运行的JVM情况,在之前的版本中提供了JFR特性,即JDK Flight Recorder。但是使用不太灵活。虽然JVM通过JFR暴露了超过500项数据,但是其中大部分数据只能通过解析JFR日志文件才能获取得到,而不是实时获取。用户想要使用JFR的数据的话,用户必须先开启JFR进行记录,然后停止记录,再将飞行记录的数据Dump到磁盘上,然后解析这个记录文件。

// 下面这条命令会立即启动JFR并开始使用templayte.jfc的配置收集60s的JVM信息,并输出到output.jfr中。
// 一旦记录完成之后,就可以复制jfr文件到你的工作环境使用jmc GUI来分析。
// 它几乎包含了排查JVM问题需要的所有信息,包括堆dump时的异常信息等。
jcmd <PID> JFR.start name=test duration=60s settings=template.jfc filename=output.jfr

这样对于应用程序分析很有效,但是对于实时监控却并不友好,因为无法将JFR采集的信息实时动态展示到仪表板上。JEP349特性能够通过异步订阅的方式直接获取JFR记录的数据,而不需要分析Dump文件。如下这段代码所示:

try (var rs = new RecordingStream()) {
rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
rs.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(10));
rs.onEvent("jdk.CPULoad", event -> {
System.out.println(event.getFloat("machineTotal"));
});
rs.onEvent("jdk.JavaMonitorEnter", event -> {
System.out.println(event.getClass("monitorClass"));
});
rs.start();
}

JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination

ParallelScavenge + SerialOld GC的GC组合要被标记为Deprecate了,也就意味着,在接下来的某个JDK版本中,会彻底不兼容这种GC组合。

JDK官方给出将这个GC组合标记为Deprecate的理由是:这个GC组合需要大量的代码维护工作,并且,这个GC组合很少被使用。因为它的使用场景应该是一个很大的Young区配合一个很小的Old区,这样的话,Old区用SerialOldGC去收集时停顿时间我们才能勉强接受。事实上,这种场景很少使用,而且风险即可。总而言之,老年代能用UseParallelOldGC ,还需要什么SerialOldGC,是吧!

JEP 363: Remove the CMS Garbage Collector

该来的总会来,自从G1横空出世后,CMS在JDK9中就被标记为Deprecate了(JEP 291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector),那么CMS被彻底移除也就是一个时间问题了。

基于Region分代是大势所趋,CMS的设计还是落后了一点,而且它的碎片化问题,给你的JVM实例就像埋了一颗炸弹。说不定哪次就在你的业务高峰期来一次FGC,这可是采用Mark—Sweep-Compact算法的SerialOldGC回收,JVM中性能最差的垃圾回收方式,停顿个几秒钟,上十秒都有可能。

当然,如果你JDK14中你还是配置的CMS(-XX:+UseConcMarkSweepGC),JVM不会报错,只是给出一个告警信息,JVM会自动回退以默认GC方式启动JVM:

Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; \
support was removed in <version>
and the VM will continue execution using the default collector.

EP 364: ZGC on macOS

很简单,就是在macOS上支持ZGC,没什么太多需要说明的。

JEP 368: Text Blocks (Second Preview)

这个特性对应JDK13的JEP 355: Text Blocks (Preview),只不过这是Second Preview而已,所以,笔者只简单解决一下这个新的语法。如果有一段SQL,老的语法是这样写的:

String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
"WHERE `CITY` = 'INDIANAPOLIS'\n" +
"ORDER BY `EMP_ID`, `LAST_NAME`;\n";

新的语法是这样写的:

String query = """
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
WHERE `CITY` = 'INDIANAPOLIS'
ORDER BY `EMP_ID`, `LAST_NAME`;
""";
如果有一段脚本需要执行,老的语法是这样的:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
" print('\"Hello, world\"');\n" +
"}\n" +
"\n" +
"hello();\n");

而新的语法是这样的:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
function hello() {
print('"Hello, world"');
}

hello();
""");


重新复习一下JDK14的9大重磅特性


想知道更多?描下面的二维码关注我


【精彩推荐】




朕已阅