JDK 11 已经刚刚进入Rampdown Phase One
,或者说减速开发第一阶段
。这将意味着主要新特性都已经冻结,以后将没有新的JEP进入JDK 11。这一阶段将侧重于加强内部稳定性,清理积存的bug和某些小型的增强。
身为新的JDK发布计划实施后的第一个长期支持版本(LTS),JDK 11无疑受到了很多Java程序员的关注。就我而言,JDK 11既有惊喜又稍微有些失望。下面我将给大家带来新版本的大致预览。
垃圾收集:精益求精
Java的垃圾收集机制虽然目前还是被一些开发者受诟病(2018年了,这样的程序员大概也很少了吧),但是Java还是拥有世界上最完善、性能最佳的垃圾收集系统。尽管目前作为默认GC的G1(Garbage First)已经可以在大多数情况下提供令人满意的吞吐量和延迟,JDK 11仍然引入了一个更加强大的GC实现,ZGC,和一个什么都不做的GC实现,Epsilon。
ZGC:承前启后,面向未来
ZGC是JDK 11中最引人注目的新特性。
OpenJDK Wiki给出ZGC的目标是:
- GC停顿时间应该小于 10毫秒
- GC停顿时间 不随堆空间的增大而增大
- 比较小的(几百MB)到非常大的(几TB)的堆空间都可以被处理
- 与G1垃圾收集器相比,不应有大于 15% 的应用程序吞吐量削减
随着现在计算机硬件的飞速发展,内存空间也在逐渐变大。尤其是在一些高负载的、承担核心工作的服务器上,动辄有上百GB内存。但是目前的G1垃圾收集器的暂停时间会随着堆内存空间的增大而增大,这样就会在比较关键的服务器中引发更加不可忍耐的GC暂停。但是ZGC做出的GC停顿时间不随堆空间的增大而增大
的承诺将会使Java更加适应新时代的硬件设备。
对NUMA(非统一内存访问架构)的识别和支持也是ZGC的亮点之一。简单的说,NUMA是一个在多处理器环境下共享内存的一种架构。但是由于Remote Access
和Local Access
之间相差数倍的访问性能,这个架构也导致了包括Mysql在内的很多数据库系统出现突然的性能滑坡。ZGC具有对NUMA的原生支持,可以让Java平台上的所有应用不用更改一行代码就可以享受到NUMA带来的优势,并且尽量规避其带来的风险。
顺便一提,ZGC是完全并行的垃圾收集器,这也意味这从前的Stop-the-World
的垃圾收集方式已经彻底消失了。
总而言之,ZGC是一个主打 服务器 方面的GC。对于普通的Java开发者或者用户来说,G1以其经过时间考验的稳定性和很好的性能还是我们的首选垃圾收集器(如果你想用100GB的内存跑Minecraft,那么土豪请随意吧)。而对于管理海量内存的服务器应用来说,ZGC无疑是非常吸引人的。也正因如此,目前的ZGC也只支持64位的Linux操作系统,毕竟其他的系统没有这种需求(如果实在需要,ZGC的开发者表示也会在其他平台上进行迁移)。
Epsilon:关掉GC有时也未必不是一个好选择
这个另类的垃圾收集器真的是什么都不干。它只管分配内存,然后没有剩余内存让JVM崩溃。
你可能会问,这东西有啥用?大概Oracle的工程师已经给你想好了应用领域:
- 性能测试:这个属于开挂吧…
- 内存压力测试:可以更好的了解程序真正使用了多少内存
- 虚拟机接口测试:简单的GC可以避免JVM测试时产生的迷惑性
- 非常短的活动工作:根本分配不完所有的堆内存
- 对GC延迟极其敏感的应用:毕竟,Epsilon是真·零延迟
- 对内存吞吐量要求很高的应用:要多少给多少
这个GC也算是Oracle对JDK10中新加入的垃圾收集器接口(JEP 304: Garbage Collector Interface)的一个示例实现,来为今后社区对GC的贡献打下了基础。
在Lambda表达式的参数中使用局部变量类型推断
除了上面两个为我们默默做出贡献的新特性,这个才是让开发者们比较兴奋的语法特性。
JDK 10中引入了var
来支持局部变量的类型推断,而在lambda表达式的参数中,却只有写类型或不写类型两个选择。一般来说,lambda表达式是省略参数类型的,但是如果要加入@Nonnull
等注解的话,参数类型又是必不可少的。所以这个JEP就应运而生了。
1 | (x, y) -> x.process(y); |
但是要注意的是,如果你要用var
,所有的参数就都得使用var
。Java不允许部分使用var
而其他不写类型或使用完整类型。
1 | (var x, y) -> x.process(y); |
标准化的HTTP客户端接口
Java原来贫乏的HttpURLConnection
等API已经在很长时间内备受程序员们的嫌弃,于是才出现像Apache HttpClient
这样的第三方库。而且随着HTTP/2,HTTPS/TLS,WebSocket等技术的普及,全新的API更是为时代所需。所以JDK 11终于带给我们了一个基于JDK 9中的孵化器项目的完整的标准化HTTP客户端API。
HTTP客户端使用Java中的NIO网络框架来实现无阻塞、高性能的异步网络通信,并通过比如java.util.concurrent.CompletableFuture
的完善的底层并发框架实现异步通信。
在JEP110(其原型项目)中,这个API提供了:
- 分离的请求和响应
- 异步通知
- 通过
SSLEngine
来提供HTTPS的支持 - 代理
- Cookies
- 身份认证
等较为基础的API。但是这个原型项目主要提供HTTP/1.1的API,对HTTP/2的支持还不甚完善。
在本JEP中,对HTTP/2的支持已经成为重中之重。并且还通过增加了对新加入的reactive-streams
的支持来提供流畅的 反应式 编程模式。相比之下的新改动有:
- 对某些API命名的改进以提高可读性
- 提供了诸如
fromXxx
ofXxx
等静态工厂方法 - 还有很多API的加入、更改和移除
关于新的HTTP客户端的使用,我会在今后对文章中详细描述。这里也提供一些资源以供参考:
基于Nest
的访问控制
在以前的private
public
protected
的基础上,JVM又提供了一种新的访问控制机制:Nest
(好吧我真的不知道这个该咋翻译,大概是一窝?)。
Java代码都是以类为单位组织的,而且即使是在一个.java
文件中的多个类也都会被分别编译为不同的.class
类文件。但是对于开发者来说,在一个源文件中的代码在逻辑上应该是可以互相访问的,但是Java并不 直接允许 这样做。我们虽然可以在一个匿名内部类中访问外层类的成员方法,但是这实际上是通过编译器自动生成的所谓 访问桥 方法进行访问。这个解决方案在解决一部分问题的同时,也带来了如增加了不必要的程序大小和额外性能损耗等问题,而且很容易迷惑一部分Java程序员。
而JDK 11带来的Nest
就是一个更完善的解决方案。编译器现在把一些 看起来 是在一个类中的代码(如上面提到的匿名内部类)组织到一 窝(Nest
) 中(真是具有想象力的命名)。而在同一个窝中的类将会拥有同等的访问权限。这会让应用程序和字节码更加简单、安全,并对开发者透明。
Java作为一种相对保守的语言,这是少数几个对《Java语言规范》和《Java虚拟机规范》做出较大改动的JEP之一。然而对于普通开发者,在通常的开发中也不会有很大的变动。但是对于 反射 的重度使用者或接触字节码工程的人来说,这个JEP还是有必要仔细了解一下的。
直接运行单个文件的Java程序
在第一次写HelloWorld.java
程序的时候,你一定运行过下面的指令:
1 | javac HelloWorld.java |
虽然看起来简单,但是Java的某些“坑”还是起到了很好劝退作用。比如没有设置好类路径,或者在java
命令后面跟了文件名而不是类名等等。
但是在JDK 11中,增强过的java
命令将可以直接运行单一文件的Java程序,这意味着你可以直接这么做:
1 | java HelloWorld.java |
而且如果你在文件开头有类似这样的语句:
1 | #!/path/to/java --source version |
那么在执行./hello
的时候,系统就会直接调用java
来执行你的代码。这样Java程序就可以和本地的工具一样轻松地使用了。
移除或废弃的API
Java EE目前已经由Oracle移交给Eclipse基金会了,而且Java EE的相关模块大概本来就不应该出现在Java SE中。所以JDK 11就永久移除了在JDK 9中标记为废弃的相关模块。关于对Java EE模块有依赖的应用程序,可以根据JEP页面上的信息进行迁移。
Nashorn是在JDK 8中引入的一个JavaScript引擎,它完整实现了ECMAScript-262 5.1标准。但是随着ECMAScript的迅速发展,Nashorn开始变得难以维护。因此JDK 11中废弃了Nashorn相关的API和jjs
工具,并将会在未来版本移除这些内容。
Pack200是在Java 5中引入、用于打包jar文件的一个压缩算法。但是JDK 9引入的Java模块化系统为我们提供了打包Java应用程序的新的工具和算法,比如jlink
连接器或jmod
运行时模块构建。因此Pack200已经没有继续存在下去的必要性了。JDK 11将把和Pack200相关的API和工具都废弃掉,并在未来移除它们。
其他特性
动态类文件常量
这个JEP扩展了Java类文件格式来支持一种新形式的常量池,CONSTANT_Dynamic
。它在初始化的时候,会像invokedynamic
指令生成代理方法一样,委托给一个初始化方法进行创建。这是一个JVM内部的特性,对上层软件没有很大的印象,但是是在为Java语言未来的重大变化铺设道路。
飞行记录器
这个JEP将会提供一组API,是我们可以以很低的性能损耗为代价,来实现Java应用程序运行时的状态监控和数据收集。这对于程序的调试和故障排查都十分有用。
低开销的堆内存概览
这个JEP是我们可以通过JVMTI
来对Java堆内存对分配进行监控和采样。这个和上面的飞行记录器一样都是一种对JVM内部进行监控对工具,只不过这个是通过本地代码(通常为C语言)来访问的。
与加密和安全相关的改进
实现传输层安全协议(TLS)的1.3版本。这个版本是对TLS的全面修订,及时实现会提高Java平台的安全性,并保持Java的竞争力。
使上述两个椭圆曲线算法满足RFC 7748的要求。
维基百科上的相关页面:
实现了RFC 7539所描述的ChaCha20
和Poly1305
加密算法。ChaCha20-Poly1305
是目前较新的流加密算法,而且现在并没有有效的攻击方式,并且对移动端更加友好。
维基百科上的相关页面:
支持Unicode 10
改进Aarch64(一种ARM处理器架构)上内建方法的实现
总结与展望
总体上来说,JDK 11这次的更新主要侧重于对JVM内部的改进,而真正影响上层开发者的特性并不是很多,因此不如JDK 9或JDK 8那样特别引入注目。但是这也有可能是oracle试图留给我们更多的时间去迁移到现代的Java版本,而且为后续Java的某些更加雄心勃勃的项目打下伏笔。
这里列举一些值得关注的项目:
Project Valhalla:一次真正的变革
这是一个一直以来呼声很高的项目(但是还是没有赶上JDK 11),因为它将会带给整个Java平台十分深远的影响。
简单来说,Valhalla将会带来:
- 对JVM的内存布局方式的改变,使其可以感知现代硬件的内存消耗模型
- “值类型”(
Value Types
)。它将会 扁平化 地、 稠密 地储存数据,使它 “用起来像类,跑起来有原生类型的性能“ - 让 泛型 支持全部类型,包括原生类型、值类型甚至是
Void
- 让已经存在的库以尽量小的代价就可以充分利用到Valhalla带来的新特性。
这是Java平台十分少见的大规模改进,但是它也会带来性能上的极大提升。但是性能并不是其唯一的目标,根据OpenJDKWiki上所说:
Yes, performance is an important part of the story – but so are safety, abstraction, encapsulation, expressiveness, maintainability, and compatible library evolution.
是的,性能的确是这个故事中的重要的一部分。但是安全性、抽象性、封装性、表达性、可维护性和兼容性也同样重要。
Project Amber:小改进带来大变化
这个计划由一组JEP组成,主要试图为Java语言提供一些现代的(其他语言早就有的)新的语法特性。JDK 10中加入的var
和JDK 11中为lambda表达式参数使用var
这两项改进都是是这个计划孵化的产物。
目前在这个计划中的其他JEP:
- JEP 302: Lambda Leftovers:对lambda表达式剩余对改进
- JEP 305: Pattern Matching:模式匹配
- JEP 325: Switch Expressions:
switch
表达式 - JEP 326: Raw String Literals:原始字符串字面量
- JEP 301: Enhanced Enums:增强的枚举类型
Project Loom:更多并发工具
这个项目主要提供一种名为”纤维(fibers
)“的更加轻量级对用户态线程实现,和对于 协程 的支持。协程也是现在比较热门的并发线程模型。这里描述了这个计划的几乎所有的改进。
Project Panama:更加完善的本地代码支持
这个项目将会增强和丰富JVM和本地代码(一般为C代码)之间的联系。它将会进一步简化Java代码和本地代码之间的沟通方式并提高调用本地代码的性能,使Java可以更加充分地利用底层的API(如OpenGL,CUDA)和底层的硬件(如对显卡等的访问),提高Java的异构运算能力和在人工智能时代的竞争力。
Project Shenandoah:超级低延迟的垃圾收集器
Java平台上优秀的GC实现还有很多。除了由Azul System在Zing中实现的著名垃圾收集器C4,Shenandoah也是另一个非常好的选择。Shenandoah项目由Red Hat公司赞助,试图打造一个开源的低延迟高性能的GC实现。它的目标和ZGC有很多相似之处,而且OpenJDK的开发人员也想测试两种GC策略在实际应用中的优劣。但是这个项目存在好几年了,还不知道它到底能不能真正进入OpenJDK中呢。
Java虽然历史久远,但是在社区和每一个开发者的帮助下,它还在在一个脚印一个脚印地踏实地向前进步。希望Java能有一个更加光明的未来!