JDK11新特性
Oracle官方于2018年9月26日宣布Java11正式发布。目前Oracle 官方也已经宣布Java11正式可以商用,这是Java 大版本周期变化后的第一个长期支持版本,非常值得关注。最新发布的Java11一共包含17个JEP(JDK Enhancement Proposals,JDK 增强提案)。对于企业来说,选择Java11将意味着长期的、可靠的、可预测的技术路线图。其中免费的OpenJDK11确定将得到OpenJDK社区的长期支持,LTS版本将是可以放心选择的版本。由于JDK9和JDK10都是一个过渡版本,JDK11的特性是在JDK9中就有的,我在博客里也做了总结 《 JDK9新特性(一)》 , 《JDK9新特性(二)》 ,其中已经介绍过的新特性将不会在这篇文章出现。
从JVM GC的角度,JDK11引入了两种新的GC,其中包括也许是划时代意义的ZGC,虽然其目前还是实验特性,但是从能力上来看,这是JDK的一个巨大突破,为特定生产环境的苛刻需求提供了一个可能的选择。例如,对部分企业核心存储等产品,如果能够保证不超过10ms的GC暂停,可靠性会上一个大的台阶,这是过去我们进行GC调优几乎做不到的,是能与不能的问题。 对于G1 GC,相比于JDK8,升级到JDK11即可免费享受到:并行的Full GC,快速的CardTable扫描,自适应的堆占用比例调整(IHOP),在并发标记阶段的类型卸载等等。这些都是针对G1的不断增强,其中串行Full GC等甚至是曾经被广泛诟病的短板,你会发现GC配置和调优在JDK11中越来越方便。 云计算时代的监控、诊断和Profiling能力,这个是相比ZGC更具生产实践意义的特性。 Java的应用场景跨度很大,从单机长时间运行的Java应用,发展成为分布式、大的单体应用或小的Function、瞬时或长时间运行等,应用场景非常复杂。
JDK11为我们提供了更加强大的基础能力,主要是两部分
第一部分:JEP 328: Flight Recorder(JFR)是Oracle刚刚开源的强大特性。JFR是一套集成进入JDK、JVM内部的事件机制框架,通过良好架构和设计的框架,硬件层面的极致优化,生产环境的广泛验证,它可以做到极致的可靠和低开销。在SPECjbb2015等基准测试中,JFR的性能开销最大不超过1%,所以,工程师可以基本没有心理负担地在大规模分布式的生产系统使用,这意味着,我们既可以随时主动开启JFR进行特定诊断,也可以让系统长期运行JFR,用以在复杂环境中进行After-the-fact
分析。
在保证低开销的基础上,JFR提供的能力可以应用在对锁竞争、阻塞、延迟,JVM GC、SafePoint等领域,进行非常细粒度分析。甚至深入JIT Compiler内部,全面把握热点方法、内联、逆优化等等。JFR提供了标准的Java、C++等扩展API,可以与各种层面的应用进行定制、集成,为复杂的企业应用栈或者复杂的分布式应用,提供All-in-One解决方案。
Flight Recorder相当于飞机的黑匣子,不会影响JVM的运行(最大性能开销不超过1%),而且是不断记录JVM的运行监控参数,而这一切都是内建在JDK和JVM内部的,并不需要额外的依赖,开箱即用。
第二部分:JEP 331: Low-Overhead Heap Profiling。它来源于Google等业界前沿厂商的一线实践,通过获取对象分配细节,为JDK补足了对象分配诊断方面的一些短板,工程师可以通过JVMTI使用这个能力增强自身的工具。
从 Java 类库发展的角度来看,JDK 11 最大的进步也是两个方面
第一部分:HTTP/2 Client API,新的HTTP API提供了对HTTP/2等业界前沿标准的支持,精简而又友好的API接口,与主流开源API(如Apache HttpClient, Jetty, OkHttp 等)对等甚至更高的性能。与此同时它是JDK在Reactive-Stream方面的第一个生产实践,广泛使用了Java Flow API等,终于让Java标准HTTP类库在扩展能力等方面,满足了现代互联网的需求。 第二部分:安全类库、标准等方面的大范围升级,其中特别是 JEP 332: Transport Layer Security (TLS) 1.3,除了在安全领域的重要价值,它还是中国安全专家范学雷所领导的JDK项目,完全不同于以往的修修补补,是个非常大规模的工程。 除此之外,JDK 还在逐渐进行瘦身工作,或者偿还 JVM、Java 规范等历史欠账,例如:Deprecate the Nashorn JavaScript Engine,它进一步明确了 Graal 很有可能将成为 JVM 向前演进的核心选择,Java-on-Java 正在一步步的成为现实。
Dynamic Class-File Constants
Java的类型文件格式将被拓展,支持一种新的常量池格式:CONSTANT_Dynamic,加载CONSTANT_Dynamic会将创建委托给bootstrap方法。其目标是降低开发新形式的可实现类文件约束带来的成本和干扰。 上面的说法可能难以理解,我将换一个方式来说明这个新特性。在JDK1.5之前,Java程序中的常量值只能是字符串或原始类型。这些常量作为文字内置在语言中,甚至由javac编译器假定以减小类文件的大小。
1class ConstantSample {
2 final String field = "foo";
3 void hello() {
4 System.out.print(field);
5 }
6}
为了表示这样的常量值,任何Java类文件都包含了一个常量池,这意味着在方法中使用或用作字段值的常量,但也包含描述类的其他不可变信息,例如类的名称或被调用方法的名称及其声明的类型名称等信息。一旦在类的常量池中记录了一个值,就可以通过指向常量池中特定条目的偏移量来引用该值。这样做,在整个类中重复的值仅需要存储一次,因为偏移量可以多次引用。 JVM甚至可以通过遍历在常量池中找到的字符串来跨类对常量字符串进行重复数据删除。 接下来我要说的是常量池存储的局限性,类文件的常量池中值的这种表示形式非常适合简单的值,例如字符串和数字等基本常量。但是同时,当javac没有发现常量时,它可能会带来非常直观的后果:
1class NoConstantSample {
2 final String field = "foo".toString();
3 void hello() {
4 System.out.print(field);
5 }
6}
尽管toString方法对于字符串来说是微不足道的,但是这种情况对于不评估Java方法的javac仍然未知。因此,编译器不能再发出常量池的值作为print语句的输入。相反,它不得不发出该字段的字段读取指令,该指令需要额外的字节,如前所述。这次,如果使用反射更改了字段的值,则调用hello方法也将打印更新的值。 虽然这是一个人为的例子,但是不难想象,在实践中如何用经典方法限制Java中的常量呢?如Math.max(CONST_A, CONST_B) 在编译的时候最大值本身就是常量,但是由于编译器无法对方法进行预估,因为也不能被编译器发现,哦!!这里原来是个常量哦!
局部变量类型推断 var
如下图,但是需要注意的是var并不是一个关键字,var仅仅是一个语法上的改进,在编译时期便已经将var转换为了对应的变量类型。然而在使用var定义变量时,必须立刻赋值,编译器能根据右边的表达式自动推断类型,所以var只是用来减少代码量的。
1public class VarTest {
2 public static void main(String[] args) {
3 Consumer<String> consumer = (@Deprecated var t) -> System.out.println(t.toUpperCase());
4 consumer.accept("tim");
5
6 //这种就是错误的,因为t没有类型
7 Consumer<String> consumer = (@Deprecated t) -> System.out.println(t.toUpperCase());
8 consumer.accept("tim");
9
10 Consumer<String> consumer = (t) -> System.out.println(t.toUpperCase());
11 consumer.accept("tim");
12 }
13}
在声明隐式类型的lambda表达式的形参时允许使用var,使用var的好处是在使用lambda表达式时给参数加上注解。
新增字符串处理 API
1public class StringAPITest {
2 public static void main(String[] args) {
3 // 判断字符串是否为空白
4 " ".isBlank(); // true
5
6 // 去除首尾空白
7 " Javastack ".strip(); // "Javastack"
8
9 // 去除尾部空格
10 " Javastack ".stripTrailing(); // " Javastack"
11
12 // 去除首部空格
13 " Javastack ".stripLeading(); // "Javastack "
14
15 // 复制字符串
16 "Java".repeat(3);// "JavaJavaJava"
17
18 // 行数统计
19 "A\nB\nC".lines().count(); // 3
20 }
21}
新增的文件 API
InputStream加强:InputStream终于有了一个非常有用的方法:transferTo,可以用来将数据直接传输到OutputStream,这是在处理原始数据流时非常常见的一种用法,如下示例:
1public class FileSystemTest {
2 public static void main(String[] args) {
3 ClassLoader loader = FileSystemTest.class.getClassLoader();
4 try (var resourceAsStream = loader.getResourceAsStream("myFile.txt")){
5 FileOutputStream outputStream = new FileOutputStream("myFile2.txt");
6 assert resourceAsStream != null;
7 resourceAsStream.transferTo(outputStream);
8 outputStream.close();
9 } catch (IOException e) {
10 e.printStackTrace();
11 }
12 }
13}
JDK11 废弃的项目
移除的项目
- 移除了com.sun.awt.AWTUtilities
- 移除了sun.misc.Unsafe.defineClass,使用java.lang.invoke.MethodHandles.Lookup.defineClass来替代
- 移除了Thread.destroy()以及 Thread.stop(Throwable)方法
- 移除了sun.nio.ch.disableSystemWideOverlappingFileLockCheck、sun.locale.formatasdefault属性
- 移除了jdk.snmp模块
- 移除了javafx,openjdk是从java10版本就移除了,oracle jdk10还尚未移除javafx,而java11版本则oracle的jdk版本也移除了javafx
- 移除了Java Mission Control,从JDK中移除之后,需要自己单独下载
- 移除了这些Root Certificates :Baltimore Cybertrust Code Signing CA,SECOM ,AOL and Swisscom
废弃选项
- -XX:+AggressiveOpts选项
- -XX:+UnlockCommercialFeatures(解锁商业性的一些组件,目前已经无需手动加上了)
- -XX:+LogCommercialFeatures选项也不再需要(Log统一了)
Unicode 10
Unicode 10增加了8518个字符,总计达到了136690个字符。并且增加了4个脚本,同时还有56个新的emoji表情符号。
Remove the JavaEE and CORBA Moudles
在Java11中移除了不太使用的JavaEE模块和CORBA技术。CORBA来自于二十世纪九十年代,Oracle说现在用CORBA开发现代Java应用程序已经没有意义了,维护CORBA的成本已经超过了保留它带来的好处。
但是删除CORBA将使得那些依赖于JDK提供部分CORBA API的CORBA实现无法运行,目前还没有第三方CORBA版本,也不确定是否会有第三方愿意接手CORBA API的维护工作。
在Java11中将Java9标记废弃的Java EE及CORBA模块移除掉,具体如下:
- java.xml.ws
- java.xml.bind
- java.xml.ws
- java.xml.ws.annotation
- jdk.xml.bind
- jdk.xml.ws被移除
只剩下java.xml、java.xml.crypto、jdk.xml.dom这几个模块;
- java.corba
- java.se.ee
- java.activation
- java.transaction等被移除
但是Java11新增一个java.transaction.xa模块
废除Nashorn javascript引擎
废除Nashorn javascript引擎,在后续版本准备移除掉,有需要的可以考虑使用GraalVM。Graal是一个用Java编写的新的JVM即时编译器,集成到HotSpot虚拟机,侧重性能和语言互操作性。Graal为Java代码提供性能优势,这得益于方法内联、流转对象分配和推理执行等新技术,从而可以实现高性能的脚本语言引擎。 与传统的HotSpot编译器不同,脚本语言解释器可以使用Graal包含的Truffle API发出原生代码,这样无需提前编译,即可让编程语言获得Java的性能。语言解释器可以使用HotSpot所用技术提高 Java 代码的速度,包括发出优化的原生代码(含去优化的条件),该技术令即时编译的代码在性能方面优于提前编译的代码。 这里是它的官网,可以看到它的官网说明文档: https://www.graalvm.org/
弃用Pack200 Tools
JDK1.5中带了一个压缩工具:Pack200,这个工具能对普通的jar文件进行高效压缩。其实现原理是根据Java类特有的结构,合并常数池,去掉无用信息等来实现对Java类的高效压缩。由于是专门对Java类进行压缩的,所以对普通文件的压缩和普通压缩软件没有什么两样,但是对于Jar文件却能轻易达到10-40%的压缩率。这在Java应用部署中很有用,尤其对于移动Java计算,能够大大减小代码下载量。 JDK1.5中还提供了这一技术的API接口,你可以将其嵌入到你的程序中使用。使用的方法很简单,下面的短短几行代码即可以实现jar的压缩和解压:
1public class VarTest {
2 public static void main(String[] args) {
3
4 //压缩
5 Packer packer = Pack200.newPacker();
6 OutputStream output=new BufferedOutputStream(new FileOutputStream(outfile));
7 packer.pack(new JarFile(jarFile), output);
8 output.close();
9
10 //解压
11 Unpacker unpacker = Pack200.newUnpacker();
12 output=new JarOutputStream(new FileOutputStream(jarFile));
13 unpacker.unpack(pack200File, output);
14 output.close();
15 }
16}
Pack200的压缩和解压缩速度是比较快的,而且压缩率也是很惊人的,在我是使用的包4.46MB压缩后成了1.44MB(0.322%),而且随着包的越大压缩率会根据明显,据说如果jar包都是class类可以压缩到1/9的大小。其实JavaWebStart还有很多功能,例如可以按不同的jar包进行lazy下载和单独更新,设置可以根据jar中的类变动进行class粒度的下载。 但是在Java11中废除了Pack200以及unpack200工具以及java.util.jar中的Pack200 API。因为Pack200主要是用来压缩jar包的工具,由于网络下载速度的提升以及Java9引入模块化系统之后不再依赖Pack200,因此这个版本将其移除掉。
Epsilon垃圾收集器
对这个特性的描述是:开发一个处理内存分配但不实现任何实际内存回收机制的GC,一旦可用堆内存用完,JVM就会退出。如果有System.gc()调用,实际上什么也不会发生(这种场景下和-XX:+DisableExplicitGC效果一样),因为没有内存回收,这个实现可能会警告用户尝试强制GC是徒劳。
用法 : -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
1class Garbage {
2 int n = (int)(Math.random() * 100);
3 @Override
4 public void finalize() {
5 System.out.println(this + " : " + n + " is dying");
6 }
7}
8public class EpsilonTest {
9 public static void main(String[] args) {
10 boolean flag = true;
11 List<Garbage> list = new ArrayList<>();
12 long count = 0;
13 while (flag) {
14 list.add(new Garbage());
15 if (list.size() == 1000000 && count == 0) {
16 list.clear();
17 count++;
18 }
19 }
20 System.out.println("程序结束");
21 }
22}
使用这个选项的原因:提供完全被动的GC实现,具有有限的分配限制和尽可能低的延迟开销,但代价是内存占用和内存吞吐量。众所周知,Java实现可广泛选择高度可配置的GC实现,各种可用的收集器最终满足不同的需求,即使它们的可配置性使它们的功能相交。有时更容易维护单独的实现,而不是在现有GC实现上堆积另一个配置选项。它的主要用途如下 :
- 性能测试(它可以帮助过滤掉GC引起的性能假象)
- 内存压力测试(例如,知道测试用例应该分配不超过1GB的内存,我们可以使用-Xmx1g –XX:+UseEpsilonGC, 如果程序有问题,则程序会崩溃)
- 非常短的JOB任务(对象这种任务, 接受GC清理堆那都是浪费空间)
- VM接口测试
- Last-drop 延迟&吞吐改进
ZGC垃圾收集器
ZGC,这应该是JDK11最为瞩目的特性,没有之一。但是后面带了Experimental,说明这还不建议用到生产环境。
ZGC,A Scalable Low-Latency Garbage Collector(Experimental),一个可伸缩低延迟的GC。GC暂停时间不会超过10ms,既能处理几百兆的小堆,也能处理几个T的大堆(OMG)。和G1相比,应用吞吐能力不会下降超过15%,为未来的GC功能和利用colord指针以及Load barriers优化奠定基础,初始只支持64位系统。
ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。GC是java主要优势之一,然而当GC停顿太长,就会开始影响应用的响应时间。消除或者减少GC停顿时长,Java将对更广泛的应用场景是一个更有吸引力的平台。此外,现代系统中可用内存不断增长,用户和程序员希望JVM能够以高效的方式充分利用这些内存,并且无需长时间的GC暂停时间。
ZGC是一个并发,基于region,压缩型的垃圾收集器,只有root扫描阶段会STW,因此GC停顿时间不会随着堆的增长和存活对象的增长而变长。
ZGC : avg 1.091ms / max:1.681ms
G1 : avg 156.806ms / max:543.846ms
用法: -XX:+UnlockExperimentalVMOptions –XX:+UseZGC
,因为ZGC还处于实验阶段,所以需要通过JVM参数来解锁这个特性。
完全支持Linux容器(包括Docker)
许多运行在Java虚拟机中的应用程序(包括Apache Spark和Kafka等数据服务以及传统的企业应用程序)都可以在Docker容器中运行。但是在Docker容器中运行Java应用程序一直存在一个问题,那就是在容器中运行JVM程序在设置内存大小和CPU使用率后,会导致应用程序的性能下降。这是因为Java应用程序没有意识到它正在容器中运行。随着Java 10的发布,这个问题总算得以解决,JVM现在可以识别由容器控制组(cgroups)设置的约束。可以在容器中使用内存和CPU约束来直接管理Java应用程序,其中包括:
- 遵守容器中设置的内存限制
- 在容器中设置可用的CPU
- 在容器中设置CPU约束
Java 10的这个改进在Docker for Mac、Docker for Windows以及Docker Enterprise Edition等环境均有效。
容器的内存限制:在Java 9之前,JVM无法识别容器使用标志设置的内存限制和CPU限制。而在Java10中,内存限制会自动被识别并强制执行。
Java将服务器类机定义为具有2个CPU和2GB内存,以及默认堆大小为物理内存的1/4。例如,Docker企业版安装设置为2GB内存和4个CPU的环境,我们可以比较在这个Docker容器上运行Java8和Java10的区别。
支持G1上的并行完全垃圾收集
对于G1 GC,相比于JDK 8,升级到 JDK11即可免费享受到:并行的Full GC,快速的CardTable扫描(这也是G1这个GC的原理),自适应的堆占用比例调整(IHOP),在并发标记阶段的类型卸载等等。这些都是针对G1的不断增强,其中串行Full GC等甚至是曾经被广泛诟病的短板,你会发现GC配置和调优在JDK11中越来越方便。
免费的低耗能飞行记录仪和堆分析仪
这个在文章开篇已经说到了,Low-Overhead Heap Profiling相当于是一个非常低耗能的黑匣子,通过JVMTI(TI即ToInterface)的SampledObjectAlloc回调提供了一个开销低的Heap分析方式,提供一个低开销的,为了排错Java应用问题,以及JVM问题的数据收集框架,希望达到的目标如下:
- 提供用于生产和消费数据作为事件的API
- 提供缓存机制和二进制数据格式
- 允许事件配置和事件过滤
- 提供OS、JVM和JDK库的事件
实现ChaCha20和Poly1305加密算法
JDK11实现了RFC7539中指定的ChaCha20和Poly1305两种加密算法,用来代替RC4。RFC7748定义的秘钥协商方案更高效,更安全。JDK增加两个新的接口:
1public class EpsilonTest {
2 public static void main(String[] args) throws Exception{
3 KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH");
4 NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
5 kpg.initialize(paramSpec);
6 KeyPair kp = kpg.generateKeyPair();
7
8 KeyFactory kf = KeyFactory.getInstance("XDH");
9 BigInteger u = new BigInteger("123456");
10 XECPublicKeySpec pubSpec = new XECPublicKeySpec(paramSpec, u);
11 PublicKey pubKey = kf.generatePublic(pubSpec);
12
13 KeyAgreement ka = KeyAgreement.getInstance("XDH");
14 ka.init(kp.getPrivate());
15 ka.doPhase(pubKey, true);
16 byte[] secret = ka.generateSecret();
17 }
18}
默认根权限证书与HTTPS安全协议TLS1.3
实现TLS协议1.3版本,TLS允许客户端和服务器端通过互联网以一种防止窃听,篡改以及消息伪造的方式进行通信。
Java Flight Recorder
Flight Recorder源自飞机的黑盒子,Flight Recorder以前是商业版的特性,在java11当中开源出来,它可以导出事件到文件中,之后可以用Java Mission Control来分析。可以在应用启动时配置java -XX:StartFlightRecording
,或者在应用启动之后,使用jcmd来录制,比如:
Flight Recorder是Oracle 刚刚开源的强大特性。我们知道在生产系统进行不同角度的 Profiling,有各种工具、框架,但是能力范围、可靠性、开销等,大都差强人意,要么能力不全面,要么开销太大,甚至不可靠可能导致Java 应用进程宕机。
而 JFR 是一套集成进入 JDK、JVM 内部的事件机制框架,通过良好架构和设计的框架,硬件层面的极致优化,生产环境的广泛验证,它可以做到极致的可靠和低开销。在 SPECjbb2015 等基准测试中,JFR 的性能开销最大不超过1%,所以,工程师可以基本没有心理负担地在大规模分布式的生产系统使用,这意味着,我们既可以随时主动开启 JFR 进行特定诊断,也可以让系统长期运行 JFR,用以在复杂环境中进行After-the-fact分析。还需要苦恼重现随机问题吗?JFR 让问题简化了很多。
在保证低开销的基础上,JFR 提供的能力也令人眼前一亮,例如:我们无需 BCI 就可以进行 Object Allocation Profiling,终于不用担心BTrace之类把进程搞挂了。对锁竞争、阻塞、延迟,JVM GC、SafePoint 等领域,进行非常细粒度分析。甚至深入JIT Compiler内部,全面把握热点方法、内联、逆优化等等。JFR 提供了标准的 Java、C++等扩展API,可以与各种层面的应用进行定制、集成,为复杂的企业应用栈或者复杂的分布式应用,提供All-in-One解决方案,而这一切都是内建在JDK和 JVM内部的,并不需要额外的依赖,开箱即用。