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)
很明显这个特性跟使用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")) { ... ... }
这个特性有点意思,绝对非常有用。想象我们有一行这样的代码,并且在这里抛出了空指针,那么,我们没办法知道空指针是由于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)
这个特性也是继承自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语法相比以前灵活了很多很多!
了解这个特性之前,我们需要了解什么是 NUMA 。NUMA就是非统一内存访问架构(英语:non-uniform memory access,简称NUMA),是一种为多处理器的电脑设计的内存架构, 内存访问时间取决于内存相对于处理器的位置 。在NUMA下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。如下图所示,Node0中的CPU如果访问Node0中的内存,那就是访问本地内存,如果它访问了Node1中的内存,那就是远程访问,性能较差:
非统一内存访问架构的特点是:被共享的内存物理上是分布式的,所有这些内存的集合就是全局地址空间。所以处理器访问这些内存的时间是不一样的,显然访问本地内存的速度要比访问全局共享内存或远程访问外地内存要快些。另外,NUMA中内存可能是分层的:本地内存,群内共享内存,全局共享内存。
JEP345希望通过实现NUMA-aware的内存分配,改进G1在大型机上的性能!现代的multi-socket服务器越来越多都有NUMA,意思是,内存到每个socket的距离是不相等的,内存到不同的socket之间的访问是有性能差异的,这个距离越长,延迟就会越大,性能就会越差!(https://openjdk.java.net/jeps/345)。只需要设置JVM参数: +XX:+UseNUMA 后, 当JVM初始化的时候(即Java应用启动的时候),G1的Region集合就会被均匀的分散到所有有效的NUMA节点上。
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(); }
ParallelScavenge + SerialOld GC的GC组合要被标记为Deprecate了,也就意味着,在接下来的某个JDK版本中,会彻底不兼容这种GC组合。
JDK官方给出将这个GC组合标记为Deprecate的理由是:这个GC组合需要大量的代码维护工作,并且,这个GC组合很少被使用。因为它的使用场景应该是一个很大的Young区配合一个很小的Old区,这样的话,Old区用SerialOldGC去收集时停顿时间我们才能勉强接受。事实上,这种场景很少使用,而且风险即可。总而言之,老年代能用UseParallelOldGC ,还需要什么SerialOldGC,是吧!
该来的总会来,自从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.
很简单,就是在macOS上支持ZGC,没什么太多需要说明的。
这个特性对应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(); """);
END