《架构师》2018年4月
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

Java 10正式发布,带来了这些新特性

作者 张建锋

北京时间3月21日,Oracle官方宣布Java 10正式发布。这是Java大版本周期变化后的第一个正式发布版本(详见这里),非常值得关注。你可以即刻下载试用。

去年9月,Oracle将Java大版本周期从原来的2-3年,调整成每半年发布一个大的版本。而版本号仍延续原来的序号,即Java 8、Java 9、Java 10、Java 11.....

但和之前不一样的是,同时还有一个版本号来表示发布的时间和是否为LTS(长期支持版本),比如Java 10对应18.3。如下示例:

/jdk-10/bin$ ./java -version

openjdk version "10" 2018-03-20

OpenJDK Runtime Environment 18.3 (build 10+46)

OpenJDK 64-Bit Server VM 18.3 (build 10+46, mixed mode)

需要注意的是Java 9和Java 10都不是LTS版本。和过去的Java大版本升级不同,这两个只有半年左右的开发和维护期。而未来的Java 11,也就是18.9 LTS,才是Java 8之后第一个LTS版本(得到Oracle等商业公司的长期支持服务)。

这种发布模式已经得到了广泛应用,一个成功的例子就是Ubuntu Linux操作系统,在偶数年4月的发行版本为LTS,会有很长时间的支持。如2014年4月份发布的14.04 LTS,Canonical公司和社区支持到2019年。类似的,Node.js,Linux kernel,Firefox也采用类似的发布方式。

Java未来的发布周期,将每半年发布一个大版本,每个季度发布一个中间特性版本。这样可以把一些关键特性尽早合并入JDK之中,快速得到开发者反馈,可以在一定程度上避免Java 9两次被迫推迟发布日期的尴尬。

下图为2017年JavaOne大会时,Oracle公开的未来Java版本发布和支持周期图。

Java 10新特性

这次发布的Java 10,新带来的特性并不多。

根据官网公开资料,共有12个JEP(JDK Enhancement Proposal特性加强提议),带来以下加强功能。

• JEP286,var局部变量类型推断。

• JEP296,将原来用Mercurial管理的众多JDK仓库代码,合并到一个仓库中,简化开发和管理过程。

• JEP304,统一的垃圾回收接口。

• JEP307,G1垃圾回收器的并行完整垃圾回收,实现并行性来改善最坏情况下的延迟。

• JEP310,应用程序类数据(AppCDS) 共享,通过跨进程共享通用类元数据来减少内存占用空间,和减少启动时间。

• JEP312,ThreadLocal握手交互。在不进入到全局JVM安全点(Safepoint) 的情况下,对线程执行回调。优化可以只停止单个线程,而不是停全部线程或一个都不停。

• JEP313,移除JDK中附带的javah工具。可以使用javac -h代替。

• JEP314,使用附加的Unicode语言标记扩展。

• JEP317,能将堆内存占用分配给用户指定的备用内存设备。

• JEP317,使用Graal基于Java的编译器,可以预先把Java代码编译成本地代码来提升效能。

• JEP318,在OpenJDK中提供一组默认的根证书颁发机构证书。开源目前Oracle提供的的Java SE的根证书,这样OpenJDK对开发人员使用起来更方便。

• JEP322,基于时间定义的发布版本,即上述提到的发布周期。版本号为\$FEATURE.\$INTERIM.\$UPDATE.\$PATCH,分别是大版本,中间版本,升级包和补丁版本。

部分特性说明

1.var类型推断

这个语言功能在其他一些语言(C#、JavaScript)和基于JRE的一些语言(Scala和Kotlin)中,早已被加入。

在Java语言很早就在考虑,早在2016年正式提交了JEP286提议。后来举行了一次公开的开发者调查,获得最多建议的是采用类似Scala的方案,“同时使用val和var”,约占一半;第二多的是“只使用var”,约占四分之一。后来Oracle公司经过慎重考虑,采用了只使用var关键字的方案。

有了这个功能,开发者在写这样的代码时:

        ArrayList<String> myList = new ArrayList<String>()

可以省去前面的类型声明,而只需要

        var list = new ArrayList<String>()

编译器会自动推断出list变量的类型。对于链式表达式来说,也会很方便:

        var stream = blocks.stream();
        ...
        int  maxWeight  =  stream.filter(b  ->  b.getColor()  ==  BLUE)
        .mapToInt(Block::getWeight).max();

开发者无须声明并且import引入Stream类型,只用stream作为中间变量,用var关键字使得开发效率提升。

不过var的使用有众多限制,包括不能用于推断方法参数类型,只能用于局部变量,如方法块中,而不能用于类变量的声明,等等。

另外,我个人认为,对于开发者而言,变量类型明显的声明会提供更加全面的程序语言信息,对于理解并维护代码有很大的帮助。一旦var被广泛运用,开发者阅读三方代码而没有IDE的支持下,会对程序的流程执行理解造成一定的障碍。所以我建议尽量写清楚变量类型,程序的易读维护性有时更重要一些。

2.统一的GC接口

在JDK10的代码中,路径为openjdk/src/hotspot/share/gc/,各个GC实现共享依赖shared代码,GC包括目前默认的G1,也有经典的Serial、Parallel、CMS等GC实现。

3.应用程序类数据(AppCDS)共享

CDS特性在原来的bootstrap类基础之上,扩展加入了应用类的CDS(Application Class-Data Sharing)支持。

其原理为:在启动时记录加载类的过程,写入到文本文件中,再次启动时直接读取此启动文本并加载。设想如果应用环境没有大的变化,启动速度就会得到提升。

我们可以想像为类似于操作系统的休眠过程,合上电脑时把当前应用环境写入磁盘,再次使用时就可以快速恢复环境。

我在自己PC电脑上做以下应用启动实验。

首先部署wildfly 12应用服务器,采用JDK10预览版作为Java环境。另外需要用到一个工具cl4cds[1],作用是把加载类的日志记录,转换为AppCDS可以识别的格式。

A安装好wildfly并部署一个应用,具有Angularjs,rest,jpa完整应用技术栈,预热后启动三次,并记录完成部署时间

分别为6716ms,6702ms,6613ms,平均时间为6677ms。

B加入环境变量并启动,导出启动类日志

        export PREPEND_JAVA_OPTS="-Xlog:class+load=debug:file=/tmp/wildfly.
        classtrace"

C使用cl4cds工具,生成AppCDS可以识别的cls格式

        /jdk-10/bin/java -cp src/classes/ io.simonis.cl4cds /tmp/wildfly.
        classtrace /tmp/wildfly.cls

打开文件可以看到内容为:

        java/lang/Object id: 0x0000000100000eb0
        java/io/Serializable id: 0x0000000100001090
        java/lang/Comparable id: 0x0000000100001268
        java/lang/CharSequence id: 0x0000000100001440
        ......
        org/hibernate/type/AssociationType id: 0x0000000100c61208 super:
        0x0000000100000eb0 interfaces: 0x0000000100a00d10 source: /home/
        shihang/work/jboss/wildfly/dist/target/wildfly-12.0.0.Final/modules/
        system/layers/base/org/hibernate/main/hibernate-core-5.1.10.Final.
        jar
        org/hibernate/type/AbstractType id: 0x0000000100c613e0 super:
        0x0000000100000eb0 interfaces: 0x0000000100a00d10 source: /home/
        shihang/work/jboss/wildfly/dist/target/wildfly-12.0.0.Final/modules/
        system/layers/base/org/hibernate/main/hibernate-core-5.1.10.Final.
        jar
        org/hibernate/type/AnyType  id:  0x0000000100c61820  super:
        0x0000000100c613e0 interfaces: 0x0000000100c61030 0x0000000100c61208
        source: /home/shihang/work/jboss/wildfly/dist/target/wildfly-
        12.0.0.Final/modules/system/layers/base/org/hibernate/main/
        hibernate-core-5.1.10.Final.jar

....

这个文件用于标记类的加载信息。

D使用环境变量启动wildfly,模拟启动过程并导出jsa文件,就是记录了启动时类的信息。

        export  PREPEND_JAVA_OPTS="-Xshare:dump  -XX:+UseAppCDS
        - X X : S h a r e d C l a s s L i s t F i l e = / t m p / w i l d f l y . c l s
        -XX:+UnlockDiagnosticVMOptions -XX:SharedArchiveFile=/tmp/wildfly.
        jsa"

查看产生的文件信息,jsa文件有较大的体积。

        /opt/work/cl4cds$ ls -l /tmp/wildfly.*
        -rw-rw-r--1 shihang shihang    8413843 Mar 20 11:07 /tmp/wildfly.
        classtrace
        -rw-rw-r--1 shihang shihang   4132654 Mar 20 11:11 /tmp/wildfly.cls
        -r--r--r--1 shihang shihang 177659904 Mar 20 11:13 /tmp/wildfly.jsa

E使用jsa文件启动应用服务器

        export  PREPEND_JAVA_OPTS="-Xshare:on  -XX:+UseAppCDS
        -XX:+UnlockDiagnosticVMOptions -XX:SharedArchiveFile=/tmp/wildfly.
        jsa"

启动完毕后记录时长,三次分别是5535ms,5333ms,5225ms,平均为5364ms,相比之前的6677ms可以算出启动时间提升了20%左右。

这个效率提升,对于云端应用部署很有价值。

以上实验方法参考于技术博客[2]

4.JEP314,使用附加的Unicode语言标记扩展

JDK10对于Unicode BCP 47有了更多的支持,BCP 47是IETF定义语言集的规范文档。使用扩展标记,可以更方便的获得所需要的语言地域环境。

如JDK10加入的一个方法,

        java.time.format.DateTimeFormatter::localizedBy

通过这个方法,可以采用某种数字样式,区域定义或者时区来获得时间信息所需的语言地域本地环境信息。

附:从链接[3]可以看到JDK10所有的方法级别改动。

5.查看当前JDK管理根证书

自JDK9起在keytool中加入参数-cacerts,可以查看当前JDK管理的根证书。而OpenJDK9中cacerts为空,这样就会给开发者带来很多不变。

EP318就是利用Oracle开源出OracleJavaSE中的cacerts信息,在OpenJDK中提供一组默认的根证书颁发机构证书,目前有80条记录。

        /jdk-10/bin$ ./keytool -list -cacerts
        Enter keystore password:   Keystore type: JKS
        Keystore provider: SUN
        Your keystore contains 80 entries
        verisignclass2g2ca [jdk], Dec 2, 2017, trustedCertEntry,
        Certificate fingerprint (SHA-256): 3A:43:E2:20:FE:7F:3E:A9:65:3D:1E:21
        :74:2E:AC:2B:75:C2:0F:D8:98:03:05:BC:50:2C:AF:8C:2D:9B:41:A1
        ......

下一版本展望

下一个Java大版本会是Java 11,也是Java 8之后的LTS版本,预计会在今年的9月份发布。目前只有四个JEP,更多加强提议会逐步加入。

这个版本会充分发挥模块化的能力,把当前JDK中的关于JavaEE和Corba的部分移除,变得更加紧凑。

虽然JDK9最大的亮点是模块化,但Java业界广泛接纳并且适应需要一个过程。当前已经有一些支持模块化的类库,如log4j2,但大多数还未支持。

可以预见JDK11发布之后,模块化特性就成为长期支持特性,会有越来越多的类库提供对模块化的支持。

Java依然会是最适合应用开发的语言和平台,庞大的社区和广泛的开发者,会不断促使Java不断完善优化,在各个编程领域继续发扬光大。

对文中引用文章原作者表示致谢!引用的图示,数据和方法都属于原作者。下一个Java大版本会是Java 11,也是Java 8之后的LTS版本,预计会在今年的9月份发布。目前只有四个JEP,更多加强提议会逐步加入。这个版本会充分发挥模块化的能力,把当前JDK中的关于JavaEE和Corba的部分移除,变得更加紧凑。虽然JDK9最大的亮点是模块化,但Java业界广泛接纳并且适应需要一个过程。当前已经有一些支持模块化的类库,如log4j2,但大多数还未支持。可以预见JDK11发布之后,模块化特性就成为长期支持特性,会有越来越多的类库提供对模块化的支持。Java依然会是最适合应用开发的语言和平台,庞大的社区和广泛的开发者,会不断促使Java不断完善优化,在各个编程领域继续发扬光大。对文中引用文章原作者表示致谢!引用的图示,数据和方法都属于原作者。

附录

[1]https://simonis.github.io/cl4cds/

[2]https://marschall.github.io/2018/02/18/wildfly-appcds.html

[3]https://gunnarmorling.github.io/jdk-api-diff/jdk9-jdk10-api-diff.html#java.time.format.DateTimeFormatter

作者介绍

张建锋,永源中间件共同创始人,原红帽公司JBoss应用服务器核心开发组成员。毕业于北京邮电大学和清华大学,曾供职于金山软件,IONA科技公司和红帽软件。

对于JavaEE的各项规范比较熟悉;开源技术爱好者,喜欢接触各类开源项目,学习优秀之处并加以借鉴,认为阅读好的源码就和阅读一本好书一样让人感到愉悦;在分布式计算,企业应用设计,移动行业应用,DevOps等技术领域有丰富的实战经验和自己的见解;愿意思考软件背后蕴涵的管理思想,认为软件技术是一种高效管理的实现方式,有志于将管理学和软件开发进行结合。