重新复习一下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中的内存,那就是远程访问,性能较差:
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();
""");
想知道更多?扫描下面的二维码关注我
【精彩推荐】
朕已阅