关于java:JDK8到JDK17有哪些吸引人的新特性

49次阅读

共计 19319 个字符,预计需要花费 49 分钟才能阅读完成。

作者:京东批发 刘一达

前言

2006 年之后 SUN 公司决定将 JDK 进行开源,从此成立了 OpenJDK 组织进行 JDK 代码治理。任何人都能够获取该源码,并通过源码构建一个发行版公布到网络上。然而须要一个组织审核来确保构建的发行版是无效的, 这个组织就是 JCP(Java Community Process)。2009 年,SUN 公司被 Oracle 公司 ” 白嫖 ”(参考 2018 年 Google 赔款),此时大家应用的 JDK 通常都是 Oracle 公司的 OpenJDK 构建版本 -OracleJDK。然而,Oracle 公司是一个显著只讲商业而不论情怀的公司,接手 Java 商标之后,显著放慢了 JDK 的公布版本。2018 年 9 月 25 日,JDK11 胜利公布,这是一个 LTS 版本,蕴含了 17 个 JEP 的更新。与此同时,Oracle 把 JDK11 起以往的商业个性全副开源给 OpenJDK(例如:ZGC 和 Flight Recorder)。依据 Oracle 的官网说法(Oracle JDK Releases for Java 11 and Later),从 JDK11 之后,OracleJDK 与 OpenJDK 的性能基本一致。而后,Oracle 发表当前将会同时发行两款 JDK:1. 一个是以 GPLv2+CE 协定下,由 Oracle 发行 OpenJDK(简称为 Oracle OpenJDK);2. 另一个是在 OTN 协定下的传统 OracleJDK。这两个 JDK 共享绝大多数源码,外围差别在于前者能够收费在开发、测试和生产环境下应用,然而只有半年工夫的更新反对。后者各个人能够收费应用,然而生产环境中商用就必须付费,能够有三年工夫的更新反对。
2021 年 9 月 14 日,Oracle JDK17 公布,目前也是最新的 Java LTS 版本。有意思的是,Oracle 居然 ” 朝令夕改 ”,OracleJDK17 居然是收费的开源协定,并撑持长达 8 年的保护打算。目前公司外部应用的 OracleJDK8 最高版本为 1.8.0.192,而 Oracle 在 JDK8 上开源协定反对的最高收费版本为 jdk1.8.0_202。2022 年 Spring6 和 SpringBoot3 相继推出,而反对的最低版本为 JDK17。综上所述,JDK8 为目前绝大多数以稳定性为主的零碎第一抉择,然而降级到高版本 JDK 也只是工夫问题。上面图表展现了 JDk8 到 JDK17 的每个版本升级的 JEP 个数。

通过以上图表,咱们能够得出结论,JDK8 到 JDK17 蕴含大量新个性,为 Oracle 在 Java 近 5 年来的智慧结晶。目前市面上的公司还是只有多数零碎会抉择 JDK11 或者 JDK17 作为线上技术选型,如果抉择从 JDK8 降级到 JDK17 必然会有十分大的挑战和较多须要填的坑。本文次要介绍 JDK8 到 JDk17 近 200 个 JEP 中比拟有价值的新个性(依照价值从高到低排序),这里有一部分个性作者也在线上环境应用过,也会将其中的应用心得分享给大家。

外围 JEP 性能及原理介绍

一、Java 平台模块化零碎(Jigsaw 我的项目)

JDK9 最夺目的新个性就是 Java 平台模块化零碎(JPMS,Java Platform Module System),通过 Jigsaw 我的项目施行。Jigsaw 我的项目是 Java 倒退过程的一个微小里程碑,Java 模块系统对 Java 零碎产生十分深远的影响。与 JDK 的函数式编程和 Lamda 表达式存在实质不同,Java 模块零碎是对整个 Java 生态系统做出的扭转。

同时也是 JDK7 到 JDK9 的第一跳票王我的项目。Jigsaw 我的项目本打算于在 2010 年随同着 JDK7 公布,随着 Sun 公司的败落及 Oracle 公司的接手,Jigsaw 我的项目从 JDK7 始终跳票到 JDK9 才公布。前后经验了前后将近 10 年的工夫。即便在 2017JDK9 公布前夕,Jigsaw 我的项目还是差点胎死腹中。起因是以 IBM 和 Redhat 为首的 13 家企业在 JCP 委员会上一手否决了 Jigsaw 我的项目作为 Java 模块化标准进入 JDK9 公布范畴的布局。起因无非就是 IBM 心愿为本人的 OSGI 技术在 Java 模块化标准中争取一席之地。然而 Oracle 公司没有任何的让步,不惜向 JCP 发去公开信,宣称如果 Jigsaw 提案无奈通过,那么 Oracle 将间接本人开发带有 Jigsaw 我的项目的 java 新版本。经验了前后 6 次投票,最终 JDK9 还是带着 Jigsaw 我的项目最终公布了。然而,令人悲观的是,Java 模块化标准中还是给 Maven、Gradle 和 OSGI 等我的项目保留了一席之地。对于用户来说,想要实现残缺模块化我的项目,必须应用多个技术相互合作,还是减少了复杂性。如果大家想要对模块化技术有更多深刻理解,举荐浏览书籍《Java9 模块化开发:外围准则与实际》

1、什么是 Java 模块化?

简略了解,Java 模块化就是将目前多个包(package)组成一个封装体,这个封装体有它的逻辑含意,同时也存在具体实例。同时模块遵循以下三个外围准则:

  1. 强封装性:一个模块能够选择性的对其余模块暗藏局部实现细节。
  2. 定义良好的接口:一个模块只有封装是不够的,还要通过对外裸露接口与其余模块交互。因而,裸露的接口必须有良好的定义。
  3. 显示依赖:一个模块通常须要协同其余模块一起工作,该模块必须显示的依赖其余模块,这些依赖关系同时也是模块定义的一部分。

2、为什么要做模块化?

模块化是分而治之的一个重要实际机制,微服务、OSGI 和 DDD 都能够看到模块化思维的影子。当初很多大型的 Java 我的项目都是通过 maven 或者 gradle 进行版本治理和我的项目构建,模块的概念在 Maven 和 gradle 中早就存在,两者的不同下文也会说到。当初让咱们一起回顾一下目前在应用 JDK 搭建简单我的项目时遇到的一些问题:

2.1 如何使得 Java SE 应用程序更加轻量级的部署?

java 包的实质只不过是类的限定名。jar 包的实质就是将一组类组合到一起。一旦将多个 Jar 包放入 ClassPath,最终失去只不过是一大堆文件而已。如何保护这么宏大的文件构造?目前最无效的形式,也是只能依赖 mave 或者 gradle 等我的项目构建工具。那最底层的 Java 平台的 Jar 包如何保护?如果我只是想部署一个简答的 helloworld 利用,我须要一个 JRE 和一个用户编译的 Jar 包,并将这个 Jar 包放到 classpath 中去。JDK9 以前,JRE 的运行依赖咱们的外围 java 类库 -rt.jar。rt.jar 是一个开箱即用的全量 java 类库,要么不应用,要么应用全副。直到 JDK8,rt.jar 的大小为 60M,随着 JDK 的继续倒退,这个包必然会越来越大。而且全量的 java 类库,给 JRE 也带来了额定的性能损耗。Java 应用程序如果能选择性的加载 rt.jar 中的文件该多好?

2.2 在裸露的 JAR 包中,如何暗藏局部 API 和类型?

在应用 Dubbo 等 RPC 框架中,provider 须要提供调用的接口定义 Jar 包,在该 Jar 包中蕴含一个共该 Jar 包外部应用的常量聚合类 Constannt,放在 constant 包内。如何能力裸露 JAR 包的同时,暗藏常量聚合类 Constant?

2.3 始终蒙受 NoClassDefFoundError 的折磨

通过什么形式,能够晓得一个 Jar 包依赖了哪些其余的 Jar 包?JDK 自身目前没有提供,能够通过 Maven 工具实现。那为什么不让 Java 平台本身就提供这些性能?

3、JPMS 如何解决现有问题?

JPMS 具备两个重要的指标:

  1. 强封装(Strong encapsulation): 每一个模块都能够申明了哪些包是对外裸露的,java 编译和运行时就能够施行这些规定来确保内部模块无奈应用外部类型。
  2. 牢靠配置(Reliable configuration):每一模块都申明了哪些是它所需的,那么在运行时就能够查看它所需的所有模块在利用启动运行前是否都有。

Java 平台自身就是必须要进行模块化革新的简单我的项目,通过 Jigsaw 我的项目落地。

3.1 Project Jigsaw
Modular development starts with a modular platform. —Alan Bateman 2016.9

模块化开始于模块化平台。Project Jigsaw 有如下几个指标:

  1. 可伸缩平台(Scalable platform):逐步从一个宏大的运行时平台到有有能力放大到更小的计算机设备。
  2. 安全性和可维护性(Security and maintainability):更好的组织了平台代码使得更好保护。暗藏外部 API 和更明确的接口定义晋升了平台的安全性。
  3. 晋升应用程序性能(Improved application performance):只有必须的运行时 runtimes 的更小的平台能够带来更快的性能。
  4. 更简略的开发体验 Easier developer experience:模块零碎与模块平台的联合使得开发者更容易构建利用和库。

对 Java 平台进行模块化革新是一个巨大工程,JDK9 之前,rt.jar 是个微小的 Java 运行时类库,大略有 60MB 左右。JDK9 将其拆分成 90 个模块左右,如下图所示(图片起源《Java 9 模块化开发》):

4 创立第一个 Java 模块

创立一个 Java 模块其实十分的简略。在目前 Maven 构造的我的项目下,只须要在 java 目录下,新建一个 module-info.java 文件即可。此时,当前目录就变成了一个 Java 模块及 Maven 模块。

--moudule1
---src
----main
-----java
------com.company.package1
------moudule-info.java
---pom.xml

5 模块化对现有利用的影响

5.1 你能够不必然而不能不懂

Java 模块化目前并没有展现出其宣传上的影响,同时也鲜有类库正在做模块化的革新。甚至,自己在创立第一个模块的时候,就遇到了 Lombook 生效、深度反射失败、Spring 启动失败以及无奈动静部署的影响。因而,尽量不要尝试在线上环境应用模块化技术 !不必,然而不代表你能够不懂!随着 Java 平台模块化的实现,运行在 JDK9 环境的 Java 程序就曾经面临着 Jar 包和模块的合作问题。防患未然,在发现问题的时候,模块化技术能够帮你疾速的定位问题并解决问题。
例如,在从 JDK8 降级到 JDK11 时,咱们常常会收到一下正告:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.jd.jsf.java.util.GregorianCalendar_$$_Template_1798100948_0 (file:/home/export/App/deliveryorder.jd.com/WEB-INF/lib/jsf-1.7.2.jar) to field java.util.Calendar.fields
WARNING: Please consider reporting this to the maintainers of com.jd.jsf.java.util.GregorianCalendar_$$_Template_1798100948_0
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

通过反射拜访 JDK 模块内类的公有办法或属性,且以后模块并未凋谢指定类用于反射拜访,就会呈现以上告警。解决形式也必须应用模块化相干常识,能够应用遵循模块化之间的拜访规定,也能够通过设置 –add-opens java.base/java.lang = ALL-UNNNAMED 毁坏模块的封装性形式长期解决;

5.2 Java 模块、Maven 模块和 OSGI 模块的之间的关系。

Java 模块化技术,实践上能够从 Java 底层解决模块和模块之间的模块依赖、多版本、动静部署等问题。前文所述,在 2017JDK9 公布前夕,以 IBM 和 Redhat 为首的 13 家企业在 JCP 委员会上一手否决了 Jigsaw 我的项目作为 Java 模块化标准进入 JDK9 公布范畴的布局。通过泛滥衡量,Java 模块化标准中还是给 Maven、Gradle 和 OSGI 等我的项目保留了一席之地。目前,能够通过 Java 模块 +Maven 模块或者 Java 模块 +OSGI 模块的形式构建我的项目,惋惜的是,应用多个技术相互合作,还是减少了复杂性。

5.3 模块化对类加载机制的影响

JDK9 之后,首先勾销了之前的扩大类加载器,这是清理之中,因为自身 JRE 扩大目录都曾经不存在,取而代之的是平台类加载器。而后,类加载器的双亲委派模型机制进行了毁坏,在子类将类委派给父类加载之前,会优先将以后类交给以后模块(Moudle)或层(Layer)的类加载器加载。所以会造成如下的类加载模型:

同时,在 JDK9 之后,引入的层(Layer)的概念,在 Java 程序启动时,会解析以后模块门路中的依赖关系,并造成一个依赖关系图蕴含在疏导层(Bootstrap Layer)中,这个依赖关系图从此开始不再扭转。因而,当初动静的新增模块要创立新的层,不同的层之间能够蕴含雷同的模块。会造成如下所示的依赖关系(图片起源《Java 9 模块化开发》):

综上所述,模块化革新对于应用自定义类加载器进行性能动态变化的程序还是微小的,一旦应用模块化,必然会导致这类性能受到微小影响。当然模块化技术遍及还须要很长一段时间,会晚然而不会不来,提前把握相干技术还是很必要。

5.4 总结

上面是 Java 模块化相干技术的一些外围脑图,能够学习参考:

二、垃圾回收器的一系列优化措施

2.1、ZGC- 新一代垃圾回收器

JDK11 中,最夺目的新个性就是 ZGC 垃圾回收器。作为试验性功能,ZGC 的特点包含:

  • GC 进展工夫不会超过 10ms。
  • 进展工夫不会随着堆的大小,或者沉闷对象的大小而减少;
  • 绝对于 G1 垃圾回收器而言,吞吐量升高不超过 15%;
  • 反对 Linux/x64、window 和 mac 平台;
  • 反对 8MB~16TB 级别的堆回收。

同时依据 openJDK 官网的性能测试数据显示(JEP333),ZGC 的体现十分的杰出:

  • 在仅关注吞吐量指标下,ZGC 超过了 G1;
  • 在最大提早不超过某个设定值(10 到 100ms)下关注吞吐量,ZGC 较 G1 性能更加突出。
  • 在仅关注低提早指标下,ZGC 的性能高出 G1 将近两个数量级。99.9th 仅为 G1 的百分之一。

也正是因为如此,ZGC 几乎是低提早大内存服务的福音。话说如此,作者在尝试应用 ZGC 过程中还是发现一些问题:

  1. 因为整个 ZGC 周期根本都是并发执行,因而创立新对象的速度与垃圾回收的速度从一开始就在较量。如果创立新对象的速度更胜一筹,垃圾会将堆占满导致局部线程阻塞,直到垃圾回收结束。
  2. G1 尽管是第一个基于全局的垃圾回收器,然而依然存在新生代和老年代的概念。然而从 ZGC 开始,齐全摈弃了新生代和老年代。然而新生代对象朝生夕灭的个性会给 ZGC 带来很大的压力。齐全的并发执行,必然会造成肯定的吞吐量升高。
  3. 在 JDK11,G1 垃圾回收器目前还只是实验性的性能,只反对 Linux/x64 平台。后续优化接改良,短时间内无奈更新到 JDK11 中,所以可能会遇到一些不稳固因素。例如: 1. JDK12 反对并发类卸载性能。2. JDK13 将可回收内存从 4TB 反对到 16TB。3. JDK14 晋升稳定性的同时,进步性能。4. JDK15 从试验个性转变为可生产个性。所以如果想要应用稳固的 ZGC 性能,只能降级到 JDK17,横跨一个 JDK11LTS 版本,同时面临近 200 个 JEP 带来的性能更新。
  4. 理论线上生产环境,在订单商品等外围零碎尝试应用 ZGC。然而压测结果显示,在 JDK11 还是 JDK17 都差强人意。当然这并不是代表 ZGC 自身技术缺点,而是须要依据不同的线上环境做更深度的调优和实际。因为数据窃密等起因,这里没有给大家展现具体的压测数据,读者能够在各自环境进行不同水平的压测验证。

ZGC 的原理介绍须要极大的篇幅,本文不打算对 ZGC 的底层技术开展大范畴探讨。如果大家想要深刻学习,作者举荐书籍《新一代垃圾回收器 ZGC 设计与实现》、Openjdk 官网:ZGC 介绍以及《深刻了解 Java 虚拟机第五版》中的一些介绍。

2.2、G1 垃圾回收器相干

总的来讲,得益于多个 JEP 优化,G1 垃圾回收器无论是在 JDK11 还是 JDK17 都体现出了更弱小的能力。随着 CMS 垃圾回收器的废除,以及新生代 ZGC 的老成持重,G1 垃圾回收器毫无疑问成了兼顾提早和吞吐的最佳抉择。通过屡次压测后果察看,只是简略的进步 JDK 版本,就能够做到更低的 GC 工夫、更短的 GC 距离以及更少的 CPU 损耗。

场景 JDK 并发 基线参考 TPS TPM TP99 TP999 TP9999 MAX CPU
1.8.0_192 20 -Xms12g -Xmx12g -XX:+UseG1GC -XX:ParallelGCThreads=13 -XX:ConcGCThreads=4 1680 97640 10 28 31 32 50.07%
11.0.8 20 -Xms12g -Xmx12g -XX:+UseG1GC -XX:ParallelGCThreads=13 -XX:ConcGCThreads=4 1714 99507 10 23 27 29 49.35%
2.2.1、G1 的 Full GC 从串行改为并行(JEP307)

​ G1 垃圾回收器,在 Mix GC 回收垃圾的速度小于新对象调配的速度时,会产生 Full GC。之前,产生 Full GC 时采纳的是 Serial Old 算法,该算法应用单线程标记 - 革除 - 压缩算法,垃圾回收吞吐量较高,然而 Stop-The-World 工夫变长。JDK10,为了缩小 G1 垃圾回收器在产生 Full GC 时对利用造成的影响,Full GC 采纳并行标记 - 革除 - 压缩算法。该算法能够通过多线程合作,缩小 Stop-The-World 工夫。线程的数量能够由 -XX:ParallelGCThreads 选项来配置,然而这也会影响 Young GC 和 Mixed GC 线程数量。

2.2.2、可中断的 Mixed-GC(JEP344)

G1 垃圾回收器,通过一种名为 CSet 的数据结构辅助实现可预测进展模型算法。CSet 中存储了 GC 过程中可进行垃圾回收的 Region 汇合。在本个性之前,CSet 一旦被确定,就必须全副扫描并执行回收操作,这可能会导致超过预期的垃圾回收暂停工夫。因而,JEP344 针对这种问题进行了优化。Java12 中将把 Cset 拆分为强制及可选两局部。无限执行强制局部的 CSet,执行实现之后如果存在剩余时间,则持续解决可选 Cset 局部,从而让 GC 暂停工夫更靠近预期值。

2.2.3 G1 反对 NUMA 技术(JEP345)

非对立内存拜访架构(英语:non-uniform memory access,简称 NUMA)是一种为多处理器的电脑设计的内存架构,内存拜访工夫取决于内存绝对于处理器的地位。在 NUMA 下,处理器拜访它本人的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。ParallelGC 在前几年曾经开始反对 NUMA 技术,并且对于垃圾回收器性能有较大晋升。惋惜的是,G1 垃圾回收器在 JDK14 之前始终不反对此项技术,当初能够通过参数 +XX:+UseNUMA 在应用 G1 垃圾回收器时应用 NUMA 技术。

2.3、废除 CMS 垃圾回收器

CMS 垃圾回收器在 JDK9 彻底被废除,在 JDK12 间接被删除。目前,G1 垃圾回收器是代替 CMS 的最优抉择之一。

2.4、废除 ParallelScavenge + SerialOld 垃圾回收器组合

Java 垃圾回收器有多种多样的组合和应用形式。上面这张图,我大略看过不差 10 遍,可是每次后果也是雷同,记不住!!!!

默认垃圾回收器是哪些?
-XX:+UseParallelGC -XX:-UseParallelOldGC -XX:+UseParallelGC -XX:+UseParNewGC 这几个参数有什么区别?
CMS 垃圾回收器有哪些要害参数?浮动垃圾怎么解决?如何防止 Full GC 产生?
好消息!这些当前都不必记忆了,咱们只须要专一攻克三款垃圾回收器原理:默认大哥 G1、新晋新星 ZGC、非亲儿子 Shanondoah(理解)。这里兴许有人会抬杠,小内存 CMS 会有更好的体现。ParNew 依然是高吞吐服务的首选。大道至简,简略易用才是王道。G1 和 ZGC 必然是当前 JVM 垃圾回收器的重点倒退方向,与其消耗精力记忆行将淘汰的技术,不如利出一孔,精通一门!

2.4、Epsilon:低开销垃圾回收器

Epsilon 垃圾回收器的指标是开发一个管制内存调配,然而不执行任何理论的垃圾回收工作。上面是该垃圾回收器的几个应用场景:性能测试、内存压力测试、极度短暂 job 工作、提早改良、吞吐改良。

三、诊断和监控相干优化

3.1 Java Flight Recorder[JEP328]

Java Flight Recorder (JFR) 从正在运行的 Java 应用程序收集诊断和剖析数据。依据 SPECjbb2015 基准压测结果显示,JFR 对正在运行的 Java 应用程序的性能影响低于 1%。对于 JFR 的统计数据,能够应用 Java Mission Control (JMC) 和其余工具剖析。JFR 和 JMC 在 JDK 8 中是商业付费性能,而在 JDK11 中都是收费开源的。

3.2 Java Mission Control [JMS]

Java Mission Control (JMC) 能够剖析并展现 Java Flight Recorder (JFR) 收集的数据,并且在 JDK 11 中是开源的。除了无关正在运行的应用程序的个别信息外,JMC 还容许用户深刻理解数据。JFR 和 JMC 可用于诊断运行时问题,例如内存透露、GC 开销、热点办法、线程瓶颈和阻塞 I/O。JMC 能够作为现有 JVM 监控工具的一个补充,做到维度更多,监控更加实时(秒级),能从多个视角监控以后 JVM 过程的性能,更加更疾速的定位并解决问题。

3.3 对立 JVM 日志(JEP158)

在以往的低版本中很难晓得导致 JVM 性能问题和导致 JVM 解体的根本原因。不同的 JVM 对日志的应用是不同的机制和规定,这就使得 JVM 难以进行调试。
解决这个问题最佳的办法:对所有的 JVM 组件引入一个对立的日志框架,这些 JVM 组件反对细粒度的和易配置的 JVM 日志。JDK8 以前罕用的打印 GC 日志形式:

-Xloggc:/export/Logs/gc.log // 输入 GC 日志到指定文件
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
3.3.1 指标:
  1. 所有日志记录的通用命令行选项。
  2. 通过 tag 对日志进行分类,例如:compiler, gc, classload, metaspace, svc, jfr 等。一条日志可能会含有多个 tag
  3. 日志蕴含多个日志级别:error, warning, info, debug, trace, develop。
  4. 能够将日志重定向到控制台或者文件。
  5. error, warning 级别的日志重定向到规范谬误 stderr.
  6. 能够依据日志大小或者文件数对日志文件进行滚动。
  7. 一次只打印一行日志,日志之间无穿插。
  8. 日志蕴含装璜器,默认的装璜器包含:uptime, level, tags,且装璜可配置。
3.3.2 如何应用
-Xlog[:option]
    option         :=  [][:[][:[][:]]]
                       'help'
                       'disable'
    what           :=  [,...]
    selector       :=  [*][=]
    tag-set        :=  [+...]
                       'all'
    tag            :=  name of tag
    level          :=  trace
                       debug
                       info
                       warning
                       error
    output         :=  'stderr'
                       'stdout'
                       [file=]
    decorators     :=  [,...]
                       'none'
    decorator      :=  time
                       uptime
                       timemillis
                       uptimemillis
                       timenanos
                       uptimenanos
                       pid
                       tid
                       level
                       tags
    output-options :=  [,...]
    output-option  :=  filecount=
                       filesize=
                       parameter=value
  1. 能够通过配置 -Xlog:help 参数,获取罕用的 JVM 日志配置形式。
  2. 能够通过 -Xlog:disable 参数敞开 JVM 日志。
  3. 默认的 JVM 日志配置如下:

    -Xlog:all=warning:stderr:uptime,level,tags
        - 默认配置
        - 'all' 即是蕴含所有 tag
        - 默认日志输入级别 warning,地位 stderr
        - 蕴含 uptime,level,tags 三个装璜
    
  4. 能够参考应用如下配置:

    JDK9 之前参数 -XX:+PrintGCDetails 可参考:

    -Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/export/Logs/gc-%t.log:time,tid,level,tags:filecount=5,filesize=50MB
       - safepoint 示意打印用户线程并发及暂停执行工夫
       - classhisto 示意 full gc 时打印堆快照信息
       - age*,gc* 示意打印包含 gc 及其细分过程日志,日志级别 info,文件:/export/Logs/gc.log。- 日志格局蕴含装璜符:time,tids,level,tags
        - default output of all messages at level 'warning' to 'stderr'
        will still be in effect
        - 保留日志个数 5 个,每个日志 50M 大小
    

    查看 GC 前后堆、办法区可用容量变动,在 JDK9 之前,能够应用 -XX::+PrintGeapAtGC, 当初可参考:

    -Xlog:gc+heap=debug:file=/export/Logs/gc.log:time,tids,level,tags:filecount=5,filesize=1M
        - 打印包含 gc 及其细分过程日志,日志级别 info,文件:/export/Logs/gc.log。- 日志格局蕴含装璜符:time,tids,level,tags
        - default output of all messages at level 'warning' to 'stderr'
        will still be in effect
        - 保留日志个数 5 个,每个日志 1M 大小
    

JDK9 之前的 GC 日志:

2014-12-10T11:13:09.597+0800: 66955.317: [GC concurrent-root-region-scan-start]
2014-12-10T11:13:09.597+0800: 66955.318: Total time for which application threads were stopped: 0.0655753 seconds
2014-12-10T11:13:09.610+0800: 66955.330: Application time: 0.0127071 seconds
2014-12-10T11:13:09.614+0800: 66955.335: Total time for which application threads were stopped: 0.0043882 seconds
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-root-region-scan-end, 0.0281351 secs]
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-mark-start]
2014-12-10T11:13:09.645+0800: 66955.365: Application time: 0.0306801 seconds
2014-12-10T11:13:09.651+0800: 66955.371: Total time for which application threads were stopped: 0.0061326 seconds
2014-12-10T11:13:10.212+0800: 66955.933: [GC concurrent-mark-end, 0.5871129 secs]
2014-12-10T11:13:10.212+0800: 66955.933: Application time: 0.5613792 seconds
2014-12-10T11:13:10.215+0800: 66955.935: [GC remark 66955.936: [GC ref-proc, 0.0235275 secs], 0.0320865 secs]

JDK9 对立日志框架输入的日志格局:

[2021-02-09T21:12:50.870+0800][258][info][gc] Using G1
[2021-02-09T21:12:51.751+0800][365][info][gc] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) 60M->5M(4096M) 7.689ms
[2021-02-09T21:12:51.751+0800][283][info][gc] GC(1) Concurrent Cycle
[2021-02-09T21:12:51.755+0800][365][info][gc] GC(1) Pause Remark 13M->13M(4096M) 0.959ms
[2021-02-09T21:12:51.756+0800][365][info][gc] GC(1) Pause Cleanup 13M->13M(4096M) 0.127ms
[2021-02-09T21:12:51.758+0800][283][info][gc] GC(1) Concurrent Cycle 7.208ms
[2021-02-09T21:12:53.232+0800][365][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 197M->15M(4096M) 17.975ms
[2021-02-09T21:12:53.952+0800][365][info][gc] GC(3) Pause Young (Concurrent Start) (GCLocker Initiated GC) 114M->17M(4096M) 15.383ms
[2021-02-09T21:12:53.952+0800][283][info][gc] GC(4) Concurrent Cycle

四、更加优雅的语法或者办法

4.1、汇合工厂办法

List,Set 和 Map 接口中,新的动态工厂办法能够创立 不可变汇合

// 创立只有一个值的可读 list,底层不应用数组
static <E> List<E> of(E e1) {return new ImmutableCollections.List12<>(e1);
}
// 创立有多个值的可读 list,底层应用数组
static <E> List<E> of(E e1, E e2, E e3) {return new ImmutableCollections.List12<>(e1, e2,e3);
}
// 创立单例长度为 0 的 Set 联合
static <E> Set<E> of() {return ImmutableCollections.emptySet();
}
static <E> Set<E> of(E e1) {return new ImmutableCollections.Set12<>(e1);
}

4.2、接口公有办法

Java 8, 接口能够有默认办法。Java9 之后,能够在接口内实现公有办法实现。

public interface HelloService {public void sayHello();
    // 默认办法
    default void saySomething(){syaEngHello();
        sayHello();};
    // 公有办法
    private void syaEngHello(){System.out.println("Hello!");
    }
}

4.3、改良的 Stream API

Java 9 为 Stream 新增了几个办法:dropWhile、takeWhile、ofNullable,为 iterate 办法新增了一个重载办法。

// 循环直到第一个满足条件后进行
default Stream takeWhile(Predicate predicate);
// 循环直到第一个满足条件后开始
default Stream dropWhile(Predicate predicate);
// 依据表达式生成迭代器
static  Stream iterate(T seed, Predicate hasNext, UnaryOperator next);
// 应用空值创立空的 Stream, 防止空指针
static  Stream ofNullable(T t);

4.4、JShell

JShell 是 Java 9 新增的一个交互式的编程环境工具。它容许你无需应用类或者办法包装来执行 Java 语句。它与 Python 的解释器相似,能够间接 输出表达式并查看其执行后果。

4.5、部分类型推断(JEP286)

JDK10 推出了部分类型推断性能,能够应用 var 作为局部变量类型推断标识符,缩小模板代码的生成,实质还是一颗语法糖。同时 var 关键字的用于与 lombok 提供的部分类型推断性能也基本相同。

public static void main(String[] args) throws Exception {var lists = List.of("a", "b", "c");
    for (var word : lists) {System.out.println(word);
    }
}

​ var 关键字只能用于可推断类型的代码地位,不能应用于办法形式参数,构造函数形式参数,办法返回类型等。标识符 var 不是关键字,它是一个保留的类型名称。这意味着 var 用作变量,办法名或则包名称的代码不会受到影响。但 var 不能作为类或则接口的名字。

​ var 关键字的应用的确能够缩小很多没必要的代码生成。然而,也存在本人的毛病:1. 当初很多 IDE 都存在主动代码生成的快捷方式,所以使不应用 var 关键字区别不大。2. 部分类型推断,不光是编译器在编译期间要推断,前面保护代码的人也要推断,会在肯定水平上减少了解老本。

4.6、规范 Java HTTP Client

应用过 Python 或者其余语言的 HTTP 拜访工具的人,都晓得 JDK 提供的 HttpURLConnection 或者 Apache 提供的 HttpClient 有如许的臃肿。简略比照一下。

python 自带的 urllib 工具:

response=urllib.request.urlopen('https://www.python.org')  #申请站点取得一个 HTTPResponse 对象
print(response.read().decode('utf-8'))   #返回网页内容

JDK:

HttpURLConnection connection = (HttpURLConnection) new URL("http://localhost:8080/demo/list?name=HTTP").openConnection();
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
log.info("response code : {}", responseCode);
// read response
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
    String line;
    while ((line = reader.readLine()) != null) {System.out.println(line);
    }
} finally {connection.disconnect();
}

Apache HttpClient:

CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// 创立 Get 申请
HttpGet httpGet = new HttpGet("http://localhost:12345/doGetControllerOne");
// 响应模型
CloseableHttpResponse response = null;
// 由客户端执行(发送)Get 申请
response = httpClient.execute(httpGet);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
System.out.println("响应状态为:" + response.getStatusLine());

Java 9 中引入了规范 Http Client API。并在 Java 10 中进行了更新的。到了 Java11,在前两个版本中进行孵化的同时,Http Client 简直被齐全重写,并且当初齐全反对异步非阻塞。与此同时它是 Java 在 Reactive-Stream 方面的第一个生产实践,其中宽泛应用了 Java Flow API。

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("http://openjdk.java.net/"))
      .build();
client.sendAsync(request, BodyHandlers.ofString())
      .thenApply(HttpResponse::body)
      .thenAccept(System.out::println)
      .join();

4.7、Helpful NullPointerExceptions(JEP358)

随着流式编程格调的风行,空指针异样成为了一种比拟难定位的 BUG。例如:

a.b.c.i = 99;
a[i][j][k] = 99;

在之前,咱们只能收到以下异样堆栈信息,而后必须借助 DEBUG 工具考察问题:

Exception in thread "main" java.lang.NullPointerException
    at Prog.main(Prog.java:5)

优化后,咱们能够失去更加优雅的空指针异样提示信息:

Exception in thread "main" java.lang.NullPointerException: 
        Cannot read field "c" because "a.b" is null
    at Prog.main(Prog.java:5)
    
Exception in thread "main" java.lang.NullPointerException:
        Cannot load from object array because "a[i][j]" is null
    at Prog.main(Prog.java:5)

4.8、更加优雅的 instance of 语法(JEP394)

以下代码是每个 Java 开发工程师的一块心病:

if (obj instanceof String) {String s = (String) obj;    // grr...
    ...
}

下面的 instanc of 语法一共做了三件事:

  1. 判断是否为 String 类型;
  2. 如果是,转成 String 类型;
  3. 创立一个名为 s 的长期变量;
    在 JDK16 中,应用模式匹配思维改良了 instance of 用法,能够做到以下优化成果:
if (obj instanceof String s) {// obj 是否为 String 类型,如果是创立长期变量 s
    // Let pattern matching do the work!
    ...
}

咱们能够看到,整体代码格调的确优雅了很多。变量 s 的作用域为满足条件的判断条件范畴之内。因而,以下应用也是非法的:

if (obj instanceof String s && s.length() > 5) {// 因为 && 具备短路性能
    flag = s.contains("jdk");
}

然而以下用法,则会报错:

if (obj instanceof String s || s.length() > 5) {    // Error!
    ...
}

正当应用,则能够达到以下成果:

// 优化应用前
public final boolean equals(Object o) {if (!(o instanceof Point))
        return false;
    Point other = (Point) o;
    return x == other.x
        && y == other.y;
}
// 优化应用后:public final boolean equals(Object o) {return (o instanceof Point other)
        && x == other.x
        && y == other.y;
}

4.9、更加优雅的 Switch 用法

Java 里有一句名言:能够用 switch 构造实现的程序都能够应用 if 语句来实现。而且 Swtich 语法在某些工程师眼里,基本没有 if 语句简洁。JDK14 中提供了更加优雅的 swtich 语法,例如:

// 之前
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 语句当成一个表达式来解决:

T result = switch (arg) {
    case L1 -> e1;
    case L2 -> e2;
    default -> e3;
};

static void howMany(int k) {
    System.out.println(switch (k) {
            case  1 -> "one";
            case  2 -> "two";
            default -> "many";
        }
    );
}

还能够配合关键字 yield,在简单解决场景里,返回指定值:

int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {int k = day.toString().length();
        int result = f(k);
        yield result;
    }
};

还有吗?其实在 JDK17 中,还提出了 Swtich 模式匹配的预览性能,能够做到更优雅的条件判断:

// 优化前
static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {formatted = String.format("double %f", d);
    } else if (o instanceof String s) {formatted = String.format("String %s", s);
    }
    return formatted;
}
// 优化后
static String formatterPatternSwitch(Object o) {return switch (o) {case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();};
}

五、字符串压缩 -Compact Strings(JEP254)

字符串是咱们日常编程中应用最频繁的根本数据类型之一。目前,字符串类底层都应用了一个字符数组来实现,每个字符应用 2 个字节(16 位)空间。实际上,大量的字符都属于 Latin- 1 字符范畴内,咱们只须要一个字节就能存储这些数据,因而这里有微小的可压缩空间;SPECjbb2005 压测结果显示对于 GC 工夫及 GC 工夫距离都有肯定水平的晋升。具体原理文档也能够参考【Oracle 对 CompackStrings 分享】

六、Java Flow API

Reactive Streams 是一套非阻塞背压的异步数据流解决标准。从 Java9 开始,Java 原生反对 Reactive Streams 编程标准。Java Flow API 是对 Reactive Streams 编程标准的 1 比 1 复刻,同时意味着从 Java9 开始,JDK 自身开始在 Reactive Streams 方向上进行逐渐革新。

七、新一代 JIT 编译器 Graal

即时编译器在进步 JVM 性能上扮演着十分重要的角色。目前存在两 JIT 编译器:编译速度较快但对编译后的代码优化较低的 C1 编译器;编译速度较慢但编译后的代码优化较高的 C2 编译器。两个编译器在服务端程序及分层编译算法中扮演着十分重要的角色。然而,C2 编译器曾经存在将近 20 年了,其中凌乱的代码以及局部蹩脚的架构使其难以保护。JDK10 推出了新一代 JIT 编译器 Graal(JEP317)。Graal 作为 C2 的继任者呈现,齐全基于 Java 实现。Graal 编译器借鉴了 C2 编译器优良的思维同时,应用了新的架构。这让 Graal 在性能上很快追平了 C2,并且在某些非凡的场景下还有更优良的体现。遗憾的是,Graal 编译器在 JDK10 中被引入,然而在 JDK17(JEP410)中被破除了,理由是开发者对其应用较少切保护老本太高。开发者也能够通过应用 GraalVM 来应用 Graal 编译器;

总结

本文介绍了 JDK9-JDK17 降级过的近 200 个 JEP 中作者狭窄角度认为价值较高的性能做了一个综述类介绍。次要目标有两个:

  1. 通过本文,大家能够对行将应用的 JDK11 及 JDK17 新个性有一个抽象的理解,心愿能够看到一些 Java 预发最近几年的倒退方向。
  2. 通过本文也能够看出,从 JDK9 到 JDK17,Java 生态还是生机勃勃。大量性能的更新意味着更优良的性能及更高效的开发效率,积极主动的尝试高版本 JDK;
    当然,JDK8 到 JDK17 还有需要优良的新个性,例如:shanondoah 垃圾回收器、Sealed Classes、Records;
    鉴于自己能力无限,文中会呈现一些破绽,心愿大家找出并斧正,让本文成长为后续 JDK17 降级的扫盲手册;

正文完
 0