导读:经过三年的开发,Java 9 终于在 9 月 21 日发布了,我们一起来看一看 Java 9 中的新特性。另外,大家可能非常关心,Java 语言如何在云计算、大数据等浪潮中快速创新、保持竞争力,本文也介绍一下 Oracle 建议的时间驱动的新发布模式。
杨晓峰,OpenJDK Committer (JDK 9 和 JDK 10 项目),2011 年加入 Oracle 北京研发中心 Java 团队,目前领导 Java 核心类库北京团队,领导或参与了 Java 9,8 和 7 的核心类库等模块的新特性测试和开发工作。个人技术兴趣广泛,主要专注于 Java 等编程语言的发展,尤其是在云计算等前沿领域的应用和演进。
很高兴能有机会通过高可用架构社区和大家交流 Java 9 的主要新特性,由于内容较多,本文仅做一个概要性介绍,希望深入讨论某个方面的更多细节,可以在本文留言探讨。
今天的分享,除了简单介绍 Java 平台模块化项目(Jigsaw ),主要是如下四部分
Java 类库新特性,比如支持的新的标准、新的 API 等。
语言和工具的变化。
JVM 领域新特性。
简短介绍 Java 未来的发布模式。
基本上是按照目前 JDK 自身组成进行的划分。另外,在开始之前,介绍一个词汇JEP(JDK Enhancement Proposals),这是 Java 9 中 Oracle 为管理新特性引入的概念,后面谈到具体特性时大家会发现每一个都有相应的 JEP 号码。
首先,我们就来看看 Jigsaw 项目。
其核心是实现 Java 平台模块化系统(JSR 376,Java Platform Module System,经常会简化为JPMS),这是个庞大的项目,今天抛砖引玉简单介绍一下。
Mark Reinhold 曾经提到,Jigsaw 试图解决的是两个基础性的问题:
一 、脆弱的、容易出问题的 classpath:
Classpath 就是个非常简陋的容器,Jar 其实也不提供任何边界。所以,各部分的依赖关系是隐式的,或者说是隐晦的。在实际应用中,可能导致所谓的 Jar Hell 问题。例如有可能会出现,应用的不同部分依赖于同一类库的不同版本,进而导致类加载出现不可预计的行为。
ClassLoading 非常复杂,有些行为是不明确的。做过 Java EE 中间件的同学,可能都还记得,某些 Java EE 服务器以讨厌的 ClassNotFoundException、NoClassDefFoundError 之类问题而臭名昭著。
二、JDK 自身是一个大的单体应用,越来越臃肿。这是一个扩展性问题,JDK 自身很难按照不同需求进行定制或者优化,同时也会衍生出安全、维护等各种问题。
所以,Jigsaw 项目的目标,主要是两点:
可靠的配置:明确模块边界和模块之间的依赖关系
强封装性:通过封装模块内部私有细节,来避免不希望发生的依赖关系,也能避免一些安全问题等。
理论上,良好封装能让开发变得更敏捷,因为彼此之间的依赖、交互是明确的,可以鼓励基于契约的开发。
Jigsaw 项目主要被分解为六个JEP:
JEP 261: Module System,实现模块化系统;
JEP 200: The Modular JDK,将JDK自身进行模块化;
JEP 201: Modular Source Code,按照模块化的形式,重构源代码,因为现有代码并不是划分到一个一个的模块里的。
JEP 220: Modular Run-Time Images,重新设计JDK和JRE的结构,定义新的URI scheme操作模块,类和资源(jrt)。
JEP 260: Encapsulate Most Internal APIs,按照模块化封装性的要求,将不希望暴露的内部API封装起来,如果确实证明是广泛需要的,就作为公共API公开出来。
JEP 282: jlink: The Java Linker。新的link工具
从开发者的角度,代码和功能主要有下面这些变化:
新增或修改工具和 API,用以在编译、链接和运行时支持模块。例如,基于 Module 的抽象,提供相应的 API 进行解析、配置或运行时操作等。
工具方面,比如,最常用的 javac/java,增加了对模块的支持,支持 module path,另外还提供了各种针对模块的选择。
Java9 新增了一个可选的链接阶段(linking Phase),可以方便地创建最小依赖关系的 Java 运行时。
大家可以看看模块化以后的 Java SE:
图 1 Java SE模块图
“java.base”是最基础的模块,核心类库的代码大部分都在这个模块。其他模块,从名字就可以大概地看出具体是哪个方面的类库。需要提醒的是,模块也可能是聚合其他模块组成,本身可能并不包含任何源代码,如“java.se”就是这样的聚合模块。
大家可能好奇,怎么查看具体某个 Java 模块的细节呢?在 Javadoc 里面就有很清晰的模块图和具体内容,比如:
http://download.java.net/java/jdk9/docs/api/java.se-summary.html
图2 java.se 模块的 Javadoc 页面
Java 通常分为编译期或者运行时,在 Java 9 中,增加了可选的链接阶段(linking phase),可以用新增的 jlink 工具来定制运行时环境。完美地应用在实际应用场景,还需要更多大家的反馈和完善。
下面是一个例子
$ jlink --module-path jmods/ \
--add-modules java.sql.rowset,java.activation \
--output myimage
$ myimage/bin/java --list-modules
java.activation@9
java.base@9
java.datatransfer@9
java.logging@9
java.naming@9
java.security.sasl@9
java.sql@9
java.sql.rowset@9
java.xml@9
$ myimage/bin/java –m company.application
对于有 Java 使用经验的同学,这些都是非常容易理解的,可以很快上手。
第二,就是 Java 类库相关新特性。
我大致进行了分类,也主要关注在核心类库和安全类库方面:
开发的工具API;
新标准或者协议的支持;
性能等方面的优化。
首先,谈谈新的工具 API。
JEP 102:Process API Updates:
Java 历史版本以前对进程操作的支持是非常有限的,比如获取当前进程的 Pid 等信息,都往往要使用些 hack 手段。 在 Java9 里,新增了ProcessHandle 这种抽象,Process 也进行了相应扩展。开发者可以利用 ProcessHandle 访问 ProcessHandle.info 数据结构,来获取类似 pid、启动参数及累计的运行时间等信息。也可以更优雅的进行进程监控、销毁之类操作。例如利用 ProcessHandle.onExit 去在进程退出时做一些动作。
这是一段简单的例子:
ProcessHandle current = ProcessHandle.current();
current.info()
.totalCpuDuration()
.ifPresent(d -> System.out.println("Total cpu duration :" + d));
current.children()
.forEach(p -> System.out.println("Pid:" + p.getPid()));
JEP 269: Convenience Factory Methods for Collections。
根据统计,在实际应用中,非常多的集合是有限元素的不可变示例。在历史版本中,需要非常繁琐的代码:
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
set = Collections.unmodifiableSet(set);
利用新的静态工厂方法,一行代码就搞定!
Set<String> alphabet = Set.of("a", "b", "c");
注意,这是 unmodifiable 不是 immutable 哦。
JEP 166: Java 并发(Concurrency)API 更新。
提供了一个最小集合的 API 以支持 Reactive Stream,也就是所谓的 Flow API (Publisher, Subscriber, Processor)。开发者可以异步的方式处理数据流(Stream)而不是直接操作线程、同步等,进而避免很多并发问题,并且开发者可以利用 Back-Pressure 机制等,良好地(Memory-efficient)处理数据压力。目前,在新的 HTTP/2 Client API 里就有使用,感兴趣的话可以看看相关源代码。与此同时,改进了 CompletableFuture API,增强一些 time-based 的操作,并支持增强扩展性,比如实现一个子类替换 executor。
还有一些相对底层的 API,比如 JEP 259:Stack-Walking API,提供高效的标准 API去遍历 stack; JEP 193:Variable Handles, VarHandle 是把 Unsafe 之类的底层 fence 操作暴露出来。
下面,我们看看新的标准和协议。
安全方面的新标准和协议:
JEP 219: 支持Datagram Transport Layer Security (DTLS)。支持DTLS version 1.0 (RFC 4347) and 1.2 (RFC 6347),以提供安全的UDP传输。目前,基于UDP实现的类似SIP或者电子游戏协议,被证明是很有实际价值的。
JEP 229: 默认keystore格式从JKS替换为PKCS12。
JEP 244: TLS Application-Layer Protocol Negotiation(ALPN) Extension, 这是完整支持HTTP/2协议的前提之一。
JEP 249: 支持OCSP Stapling for TLS,减少证书状态验证的网络开销,提高性能。
JEP 273: 实现基于DRBG的SecureRandom,对于Deterministic Random Bit Generator (DRBG) 机制,您可以参考NIST 800-90Ar1相关文档。
JEP 287: SHA-3 Hash Algorithms。
网络方面,主要有JEP 110: HTTP/2 client API,支持 HTTP 1.1,HTTP/2 和 WebSocket协议,实现了全新的 HTTP client API,用于替换老旧的HttpURLConnection。这个 API 是一个高性能、简化、轻量级的 API,同时支持同步和异步操作模式,充分利用了 Reactive style 编程。但是,在 Java 9 中,还处于 incubator 阶段,未来版本进一步完善,并在恰当时机转变为标准 API。
我这里有些举例的代码片段:
HttpClient client = HttpClient.newBuilder()
.sslContext(sslContext)
.version(HTTP_2)
.build();
HttpRequest req = HttpRequest.newBuilder(uri)
.POST()
.build();
client.sendAsync(req, abodyhandler)
.thenApply(…);
可以看到广泛使用了Builder模式,fluent风格。
编码方面,Java SE 9 支持:
JEP 227: Unicode 7.0
JEP 267: Unicode 8.0
JEP 226: UTF-8 Property Files,在历史版本中,属性文件是基于ISO-8859-1,不支持的字符需要显示的替换为转义序列,Java 9 改进了属性文件和ResourceBundle API以支持 UTF-8
API性能优化,我想介绍下面几点:
JEP 254:Compact String,Java 9 中,修改 String 实现,以 byte[] 数组和一个编码标记替换 char[] (16 bits) 数组,这样 Latin 语系编码字符串会节省近半空间。这个修改对 API 的使用者完全透明。
JEP 232:提高安全应用性能。 开启security manager通常会导致10-15%的性能下降,Java 9 的改进显著降低了开销。如果有兴趣可以看看具体的代码,具体的优化包括用标准的并发容器替换自定义的同步逻辑,或者去掉不必要的检查等等。
JEP 24:利用CPU指令优化GHASH和RSA。这是充分利用Intel x64或者SPARC CPU的部分新指令。部分加密函数的性能提高非常显著,比如在 benchmark 中,相比于 JDK 8,AES 性能提高了至少 8 倍。这是相对保守的数据,测试数据大多在几十倍提升。
第三,语言和工具。
JEP 222: jshell: The Java Shell (Read-Eval-Print Loop -REPL)
Jshell 是一个有趣的新工具,可以简单易用的交互式执行 Java 代码。我们马上试试看,打开 command-line,然后执行“Path-to-JDK-9\bin\jshell”,就会出现一个交互界面,您可以输入 /help 来查看各种选项。
下面,我们实验一下前文介绍的 ProcessHandle API:
图3 jshell示例
JEP 238: Multi-Release JAR Files,扩展了JAR文件格式,允许不同版本class文件共存,支持为不同JDK版本提供不同的代码实现。因为某些产品需要支持不同版本的Java,具体实现可能出现不可避免的区别,具体JAR文件结构如下图:
图4 新的多版本JAR文件结构
Java 9提供了新的javac选项“--release”, "Javac --release N"在语义上等价于:"Javac -source N -target N -bootclasspath rtN.jar"。历史版本的信息被压缩保存在jdk里面,这个大大简化了多版本编译的问题。
JEP 225: Javadoc Search,这是个不起眼,但是非常实用的功能,个人非常喜欢。可以看我下面的示例,马上试试吧.
图5 javadoc搜索
另外, 语言方面,Java 9 处理了 Project Coin / JSR 334 (Java SE 7)的一些遗留问题:
允许 @SafeVargs 使用在private instance methods
允许 effectively-final变量用于 try-with-resources语句
如果类型推断有效,允许”<>”用于匿名类。
“_” 不再是合法的identifier名称,大家可以打开 IDE 试试,呵呵。
支持private interface methods,这主要是适用于抽象interface method中的内部共用部分逻辑。
第四,JVM 新特性
JEP 248: 将 G1 作为默认垃圾收集器。目前 server 模式的默认选项是 ParallelGC(吞吐量优先)。一般认为通用场景中,延迟比吞吐量更能提高用户体验,G1 可以直接设定延迟目标,达到延迟 SLA 要求。而且最坏场景的延迟表现优于 CMS(设计原理导致碎片化问题)。
JEP 295: Ahead-of-Time Compilation,所谓的 AOT。利用新的编译工具 jaotc,可以直接把class编译成类似类库的文件。举例:
jaotc –output libHelloWorld.so HelloWorld.clas
然后使用时,只要:
java -XX:AOTLibrary=./libHelloWorld.so HelloWorld
AOT可以大大提高启动性能,目前还是实验阶段,支持Linux x64平台。
JEP 214: 移除过时的GC组合,移除在JDK 8中标记过时的Incremental CMS (iCMS),大家可以参考下图:
图6 移除GC选项说明
注意,输入 Java 不支持的参数,不再是 warning,而是 error,直接在启动阶段就不再允许。
另外, GC 方面还有一个一定潜在影响的 JEP 291:Deprecate CMS,在 Java 9 开发阶段进行的沟通中,并没有社区成员承诺承担维护 CMS 的责任。建议 CMS 使用者可以开始考虑切换到 G1 等垃圾收集器。
JEP 158 / JEP 271: 统一日志( JVM / GC ),引入适用于 JVM 各个模块的通用日志机制,并在此基础上解决过于碎片化的 JVM 日志选项。
一些 JVM 性能优化工作,大家可能会感兴趣,比如:
JEP 143: 改进竞争锁( Contended Locking)改进高度竞争( high-contended)的 Java Object Monitor 性能。高度竞争可以理解为多个(大量)线程同时试图获取一个锁,在 Java9 里,实现了更快的 Java monitor enter, exit, notify / notifyAll 等。
JEP 285: Spin-Wait Hints。 Thread 新增一个方法 onSpinWait()。用于优化程序,尤其是运行在多核 CPU 时,在 busy-locking 时的性能表现,提高反应速度。但是注意,它需要利用 x86 的 pause 指令,所以前提是相应的 CPU 支持这个指令,否则,和现有行为没有区别。
前面谈了好多 Java9 新特性,我们看看 Java 未来发布模式的可能变化,主要参考了相关博客:
https://blogs.oracle.com/Java-platform-group/faster-and-easier-use-and-redistribution-of-java-se
该文透露出主要有几个重要信息:
从 JDK9 GA, Oracle 计划发布 OpenJDK builds 基于 GPL。
Oracle 将逐步开源 Java Flight Recorder 之类的商业特性贡献给 Open JDK。
建议未来会切换为以时间驱动的发布模式,六个月的发布周期针对企业需求,以三年为周期发布 Long Term Support 版本
在刚刚结束的 JavaOne 大会上,除了 Java 领域本身的进一步开源和支持, Oracle 还现场源了非常棒的 serverless 框架, project Fn,该框架也将 Java 作为首选支持的编程语言,这些都是令人兴奋的积极变化。
Q&A
(作者注:下面是部分问答,特别强调,任何表述仅代表个人观点,希望集中于技术交流)
提问 1: Java9 怎么避免 jar hell?
杨晓峰:建议使用 JPMS 模块化,明确地定义模块的边界和依赖。当然,这样说比较范范,后续还是需要更多社区的实践和反馈。现在还有很多场景,并不在 JSR 376 的职责范围,比如,真正意义上的模块多版本。当前,加载不同版本的模块,可以尝试创建不同 Layer 的方式来实现。
提问 2: aot 可以直接编译到本地可执行文件吗 还是也需要 JRE 来执行? 还有现在 aot 的兼容性何如?
杨晓峰:不能直接编译成为可执行文件,这不是 jaotc 目前的职责;如果要定制和打包最终可执行的应用,目前还是需要 jlink 或者 javapackager 之类的工具。另外 aot 还是实验阶段,最初的版本只支持 Linux x64。
提问 3: Java 的 pattern matching 大概会在那个版本推出?
杨晓峰:这个特性 Brain Goetz 已经在社区里提过,有兴趣可以关注 OpenJDK 的相关邮件列表,获取最近进展,我无法提供任何有关版本和日程方面的信息。
提问 4:想请问 Oracle 和 JCP 之间是怎样的协作关系, JCP 有自己的 JSR, Oracle 推出了 jep,看 jep0 是说在提交 jsr 前给开发者一个更充分的准备,但不太清楚这两者之间如何协作, jep 会升级为 jsr 吗?可否介绍一下这几个概念
杨晓峰:坦白说 JCP 流程方面的东西我不是特别理解,也不好过多评价。 JEP 不是 JSR 的替代,它更是一个跟踪建议、设计、开发流程的概念。 JSR 是更正式的规范,其他公司可以根据这个 specification 做相应的参考实现。
提问 5: Java9 模块化能够很好的解决 class 的依赖问题,不过靠 maven 我们我们已经可以去解决依赖问题, Java9 的 module-info 对于开发应用有什么好处呢?对于 JDK 本身的好处似乎比较大?从应用层面来说,似乎现有的 maven 已经够用了。
杨晓峰:个人认为,这是不同层面的问题, JPMS 不能替代 build 工具的部分功能 , 而且,确实项目的初衷之一,就是解决 JDK 自身的模块化。对于好处,我可以举个,我现在写一个 serverless 应用,利用 jlink 工具去掉不必要的依赖, Docker image 只有几十兆大小,这在云计算环境并不是无所谓的。 Maven 也第一时间发布了对 Java 9 的支持, Java 社区和 Apache 等开源组织或者第三方公司有非常好的沟通。
提问 6: O 记是要让大家免费使用 JFR 了吗?
杨晓峰:免费不是技术问题,个人也无法回答。我谈到的是开源方面的信息,根据上面提到的博客( https://blogs.oracle.com/Java-platform-group/faster-and-easier-use-and-redistribution-of-java-se),未来会开源JFR等商业特性, Oracle JDK 和 Open JDK 做到 zero difference。可以看出 Oracle 对 Java 的支持是不予余力的,毕竟 Oracle JDK 有很多厉害的工具或者特性,具备很多同类产品没有的能力。
提问 7: Java 有计划对“协程”提供 runtime 级别的支持嘛?
杨晓峰:我注意到 OpenJDK 有相关项目提案,比如 http://cr.openjdk.Java.net/~rpressler/loom/Loom-Proposal.html
提问 8: kotlin 可能替代 Java 吗?你是怎么看待两个语言未来的发展方向?
杨晓峰:这个我不能做任何评价, " 臣妾真的做不到啊”。不要忽略了 , 全球至少 1200 万的 Java 开发人员和社区 , 还有海量的高质量类库、工具等等,这也代表了公司和机构天文数字的投入。历史上太多开源项目或语言起起伏伏,真正能够经久不衰的, Java 算是一个。
54 个架构案例 49 位作者 2 年打磨
『高可用架构』第 1 卷 10 月上市
点击 阅读原文 抢先预订
高可用架构
改变互联网的构建方式
长按二维码 关注「高可用架构」公众号