20220606 JDK 9-15 新版本特性
参考资料
- Java语言高级-Java8/9/10/11/12/13新特性-2019-尚硅谷 - B站
- 尚硅谷宋红康Java14新特性,jdk14于2020.3.17正式发布 - B站
- Java JDK15新特性全方位解读 - B站
- Java 9 新特性快速预览
- Java 8的新特性
- Java 7的新特性
- Java 6的新特性
- Java 5的新特性
- openjdk
概述
1. 关于 Java 生态圈
Java 是目前应用最为广泛的软件开发平台之一。随着 Java 以及 Java 社区的不断壮大, Java 也早已不再是简简单单的一门计算机语言了,它更是一个平台、一种文化、一个社区。
作为一个平台, Java 虚拟机扮演着举足轻重的作用。除了 Java 语言,任何一种能够被编译成字节码的计算机语言都属于 Java 这个平台。 Groovy 、 Scala 、 JRuby 、 Kotlin 等都是 Java 平台的一部分,它们依赖于 Java 虚拟机,同时, Java 平
台也因为它们变得更加丰富多彩。
作为一种文化, Java 几乎成为了 “开源”的代名词。在 Java 程序中,有着数不清的开源软件和框架。如 Tomcat 、 Struts ,Hibernate , Spring 等。就连 JDK 和 JVM 自身也有不少开源的实现,如 OpenJDK 、 Apache Harmony 。可以说,“共享” 的精神在 Java 世界里体现得淋漓尽致。
作为一个社区, Java 拥有全世界最多的技术拥护者和开源社区支持,有数不清的论坛和资料。从桌面应用软件、嵌入式开发到企业级应用、后台服务器、中间件,都可以看到 Java 的身影。其应用形式之复杂、参与人数之众多也令人咋舌。可以说, Java 社区已经俨然成为了一个良好而庞大的生态系统。其实这才是 Java 最大的优势和财富。
2. Java 老矣,尚能饭否?
2.1. Java 是最好的语言吗?
2022-06 的 TIOBE 社区的语言热度排行榜:
不是,因为在每个领域都有更合适的编程语言。
- C 语言无疑是现代计算机软件编程语言的王者,几乎所有的操作系统都是 C 语言写成的。 C++ 是面向对象的 C 语言,一直在不断的改进。
- JavaScript 是能运行在浏览器中的语言,丰富的前端界面离不开 JavaScript 的功劳。近年来的 Node.js 又在后端占有一席之地。
- Python 用于系统管理,并通过高性能预编译的库,提供 API 来进行科学计算,文本处理等,是 Linux 必选的解释性语言。
- Ruby 强于 DSL (领域特定语言),程序员可以定义丰富的语义来充分表达自己的思想。
- Erlang 就是为分布式计算设计的,能保证在大规模并发访问的情况下,保持强壮和稳定性。
- Go 语言内置了并发能力,可以编译成本地代码。当前新的网络相关项目,很大比例是由 Go 语言编写的,如 Docker 、 Kubernetes 等。
- 编写网页用 PHP ,函数式编程有 Lisp ,编写 iOS 程序有 Swift/Objective-C 。
一句话概括,能留在排行榜之上的语言,都是好的语言,在其所在的领域能做到最好。
2.2. Java 语言到底有什么优势?
其一,语法比较简单,学过计算机编程的开发者都能快速上手, Java 是一门极好的初学者入门语言。和 C/C++ 相比,Java 在设计上有着绝对的优势,开发人员可以尽快从语言本身的复杂性中解脱出来,将更多的精力投向软件自身的业务功能。
其二,在若干领域都有很强的竞争力,比如服务端编程,高性能网络程序,企业软件事务处理,分布式计算,Android 移动终端、嵌入式设备应用开发等等。
最重要的一点是符合工程学的需求,我们知道现代软件都是协同开发,那么代码可维护性,编译时检查,较为高效的运行效率,跨平台能力,丰富的 IDE ,测试,项目管理工具配合。都使得 Java 成为企业软件公司的首选,也得到很多互联网公司的青睐。
其三,没有短板,容易从市场上找到 Java 软件工程师。软件公司选择 Java 作为主要开发语言,再在特定的领域使用其他语言协作编程,这样的组合选择,肯定是不会有大的问题。
所以综合而言, Java 语言全能方面是最好的。
随着 Java 每半年更新一次的脚步, Java 的新版本中也出现了越来越多与其他语言相似的特性,博采众长的 Java ,还能继续保持生机。
但是, Java 在不少地方依然受到了广大开发人员的诟病,它烦琐的语法经常受到 Python 等开发人员的耻笑。在语言的动态性上,甚至也远远不如和它年龄相仿的 PHP 语言。 但为了支持动态语言, Java 虚拟机推出了新的函数调用指令 invokedynamic
,试图弥补 Java 在动态调用上的不足。
3. JDK 各版本主要特性
JDK Version 1.0
1996-01-23 Oak (橡树)
初代版本,伟大的一个里程碑,但是是纯解释运行,使用外挂 JIT ,性能比较差,运行速度慢。
JDK Version 1.1
1997-02-19
- JDBC ( Java DataBase Connectivity );
- 支持内部类;
- RMI ( Remote Method Invocation ) ;
- 反射;
- Java Bean ;
JDK Version 1.2
1998-12-08 Playground (操场)
-
集合框架;
-
JIT ( Just In Time )编译器;
-
对打包的 Java 文件进行数字签名;
-
JFC ( Java Foundation Classes ), 包括 Swing 1.0 , 拖放和 Java2D 类库;
-
Java 插件;
-
JDBC 中引入可滚动结果集, BLOB , CLOB ,批量更新和用户自定义类型;
-
Applet 中添加声音支持
同时, Sun 发布了 JSP/Servlet 、 EJB 规范,以及将 Java 分成了 J2EE 、 J2SE 和 J2ME 。 这表明了 Java 开始向企业、桌面应用和移动设备应用 3 大领域挺进。
JDK Version 1.3
2000-05-08 Kestrel(红隼)
-
Java Sound API ;
-
jar 文件索引;
-
对 Java 的各个方面都做了大量优化和增强;
此时, Hotspot 虚拟机成为 Java 的默认虚拟机。
JDK Version 1.4
2002-02-13 Merlin(隼)
-
断言;
-
XML 处理;
-
Java 打印服务;
-
Logging API ;
-
Java Web Start ;
-
JDBC 3.0 API ;
-
Preferences API ;
-
链式异常处理;
-
支持 IPV6 ;
-
支持正则表达式;
-
引入 Image I/O API
同时,古老的 Classic 虚拟机退出历史舞台。
一年后, Java 平台的 Scala 正式发布,同年 Groovy 也加入了 Java 阵营。
JAVA 5
2004-09-30 Tiger(老虎)
- 类型安全的枚举;
- 泛型;
- 自动装箱与自动拆箱;
- 元数据(注解);
- 增强循环,可以使用迭代方式;
- 可变参数;
- 静态引入;
- Instrumentation;
同时 JDK 1.5 改名为 J2SE 5.0
JAVA 6
2006-12-11 Mustang(野马)
-
支持脚本语言;
-
JDBC 4.0API ;
-
Java Compiler API ;
-
可插拔注解;
-
增加对 Native PKI ( Public Key Infrastructure ), Java GSS ( Generic Security
-
Service ), Kerberos 和 LDAP ( Lightweight Directory Access Protocol )支持;
-
继承 Web Services ;
- 同年, Java 开源并建立了 OpenJDK 。顺理成章, Hotspot 虚拟机也成为了 OpenJDK 中的默认虚拟机。
- 2007 年, Java 平台迎来了新伙伴 Clojure
- 2008 年, Oracle 收购了 BEA ,得到了 JRockit 虚拟机。
- 2009 年, Twitter 宣布把后台大部分程序从 Ruby 迁移到 Scala ,这是 Java 平台的又一次大规模应用。
- 2010 年, Oracle 收购了 Sun ,获得最具价值的 Hotspot 虚拟机。此时, Oracle 拥有市场占用率最高的两款虚拟机Hotspot 和 JRockit ,并计划在未来对它们进行整合。
JAVA 7
2011-07-28 Dolphin(海豚)
-
钻石型语法(在创建泛型对象时应用类型推断);
-
支持 try-with-resources (在一个语句块中捕获多种异常);
-
switch 语句块中允许以字符串作为分支条件;
-
引入 Java NIO.2 开发包;
-
在创建泛型对象时应用类型推断;
-
支持动态语言;
-
数值类型可以用二进制字符串表示,并且可以在字符串表示中添加下划线;
-
null 值的自动处理;
在 JDK 1.7 中,正式启用了新的垃圾回收器 G1 ,支持了 64 位系统的压缩指针。
JAVA 8
2014-03-18
-
Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中。
-
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象(实例)的方法或构造器。与 lambda 联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
-
默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
-
新工具 − 新的编译工具,如: Nashorn 引擎 jjs 、 类依赖分析器 jdeps 。
-
Stream API − 新添加的 Stream API ( java.util.Stream ) 把真正的函数式编程风格引入到 Java 中。
-
Date Time API − 加强对日期与时间的处理。
-
Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
-
Nashorn , JavaScript 引擎 − Java 8 提供了一个新的 Nashorn JavaScript 引擎,它允许我们在 JVM 上运行特 定的 JavaScript 应用。
JAVA 9
2017-09-22
-
模块系统:模块是一个包的容器, Java 9 最大的变化之一是引入了模块系统( Jigsaw 项目)。
-
REPL ( JShell ):交互式编程环境。
-
HTTP 2 客户端: HTTP/2 标准是 HTTP 协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流 以及服务器推送特性。
-
改进的 Javadoc : Javadoc 现在支持在 API 文档中的进行搜索。另外, Javadoc 的输出现在符合兼容 HTML5 标准。
-
多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
-
集合工厂方法: List , Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
-
私有接口方法:在接口中使用
private
私有方法。我们可以使用private
访问修饰符在接口中编写私有方法。 -
进程 API : 改进的 API 来控制和管理操作系统进程。引进
java.lang.ProcessHandle
及其嵌套接口Info
来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。 -
改进的 Stream API :改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
-
改进 try-with-resources :如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
-
改进的弃用注解
@Deprecated
:注解@Deprecated
可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。 -
改进钻石操作符( Diamond Operator ) :匿名类可以使用钻石操作符( Diamond Operator )。
-
改进
Optional
类:java.util.Optional
添加了很多新的有用方法,Optional
可以直接转为 Stream -
多分辨率图像 API :定义多分辨率图像 API ,开发者可以很容易的操作和展示不同分辨率的图像了。
-
改进的
CompletableFuture
API :CompletableFuture
类的异步机制可以在ProcessHandle.onExit
方法退出时执行操作。 -
轻量级的 JSON API :内置了一个轻量级的 JSON API
-
响应式流( Reactive Streams ) API : Java 9 中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。
JAVA 10
2018-03-21
- 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
,分别是大版本,中间版本,升级包和补丁版本。
JAVA 11
2018-09-25
- 181 : Nest-Based Access Control (基于嵌套的访问控制)
- 309 : Dynamic Class-File Constants (动态的类文件常量)
- 315 : Improve Aarch64 Intrinsics (改进 Aarch64 Intrinsics )
- 318 : Epsilon : A No-Op Garbage Collector ( Epsilon 垃圾回收器,又被称为" No-Op (无操作)"回收器)
- 320 : Remove the Java EE and CORBA Modules (移除 Java EE 和 CORBA 模块, JavaFX 也已被移除)
- 321 : HTTP Client ( Standard )
- 323 : Local-Variable Syntax for Lambda Parameters (用于 Lambda 参数的局部变量语法)
- 324 : Key Agreement with Curve25519 and Curve448 (采用 Curve25519 和 Curve448 算法实现的密钥协议)
- 327 : Unicode 10
- 328 : Flight Recorder (飞行记录仪)
- 329 : ChaCha20 and Poly1305 Cryptographic Algorithms (实现 ChaCha20 和 Poly1305 加密算法)
- 330 : Launch Single-File Source-Code Programs (启动单个 Java 源代码文件的程序)
- 331 : Low-Overhead Heap Profiling (低开销的堆分配采样方法)
- 332 : Transport Layer Security ( TLS ) 1.3 (对 TLS 1.3 的支持)
- 333 : ZGC : A Scalable Low-Latency Garbage Collector ( Experimental )( ZGC :可伸缩的低延迟垃圾回收器,处于实验性阶段)
- 335 : Deprecate the Nashorn JavaScript Engine (弃用 Nashorn JavaScript 引擎)
- 336 : Deprecate the Pack200 Tools and API (弃用 Pack200 工具及其 API )
4. Java 发布计划
4.1. JDK 各版本支持周期
为了更快地迭代, Java 的更新从传统的以特性驱动的发布周期,转变为以时间驱动的( 6 个月为周期)发布模式 —— 每半年发布一个大版本,每个季度发布一个中间特性版本,并且承诺不会跳票。通过这样的方式,开发团队可以把一些关键特性尽早合并到 JDK 之中,以快速得到开发者反馈,在一定程度上避免出现像 Java 9 这样两次被迫延迟发布的窘况。
按照官方的说法,新的发布周期会严格遵循时间点,将于每年的 3 月份和 9 月份发布。所以 Java 11 的版本号是18.9 ( LTS , long term support )。 Oracle 直到 2023 年 9 月都会为 Java 11 提供技术支持,而补丁和安全警告等扩展支持将持续到 2026 年。
新的长期支持版本每三年发布一次,根据后续的发布计划,下一个长期支持版 Java 17 将于 2021 年发布。
4.2. 版本升级的破坏性
Oracle 的官方观点认为:与 Java 7->8->9 相比,Java 9->10->11的升级和 8->8u20->8u40 更相似。
新模式下的 Java 版本发布都会包含许多变更,包括语言变更和 JVM 变更,这两者都会对 IDE 、字节码库和框架产生重大影响。此外,不仅会新增其他 API ,还会有 API 被删除(这在 Java 8 之前没有发生过)。
11 -> 12 -> 13 与 8u20 -> 8u40 等这样的更新主要区别在于对字节码版本的更改以及对规范的更改,对字节码版本的更改往往特别具有破坏性,大多数框架都大量使用与每个字节码版本密切相关的 ASM 或 ByteBuddy 等库。而 8u20 -> 8u40 仍然使用相同的 Java SE 规范,具有所有相同的类和方法,不同于从 Java 12 移动到 13。
Java 新的开发规则现在声明可以在一个版本中弃用某个 API 方法,并在下一个版本中删除它。
5. 从哪几个角度学习新特性
- 语法层面:lambda表达式,switch,自动装箱、自动拆箱,enum,<>,接口中的默认方法、静态方法、私有方法
- API 层面:Stream API、新的日期时间、Optional、String、集合框架
- 底层优化:JVM的优化,元空间,GC,GC的参数,js的执行引擎
Java 9
Java 9 的新特性概述
JDK 9 的发布
- 经过 4 次跳票,历经曲折的 Java 9 终于终于在 2017 年 9 月 21 日发布。
- 从 Java 9 这个版本开始, Java 的计划发布周期是 6 个月,下一个 Java 的主版本将于 2018 年 3 月发布,命名为 Java 18.3 (Java 10),紧接着再过六个月将发布 Java 18.9 (Java 11)。
- 这意味着 Java 的更新从传统的以特性驱动的发布周期,转变为以时间驱动的( 6 个月为周期)发布模式,并逐步的将 Oracle JDK 原商业特性进行开源。
- 针对企业客户的需求, Oracle 将以三年为周期发布长期支持版木( long term support )
- Java 9 提供了超过 150 项新功能特性,包括备受期待的模块化系统、可交互的 REPL 工具: jshell , JDK 编译工具, Java 公共 API 和私有代码,以及安全增强、扩展提升、性能管理改善等。可以说 Java9 是一个庞大的系统工程,完全做了一个整体改变。
Java 9 中有哪些不得不说的新特性?
- 模块化系统
- jShell 命令
- 多版本兼容 jar 包
- 接口的私有方法
- 钻石操作符的使用升级
- 语法改进:try 语句
- String 存储结构变更
- 便利的集合特性:
of()
- 增强的
Stream
API - 全新的 HTTP 客户端 API
- Deprecated 的相关 API
- javadoc 的 HTML5 支持
- Javascript 引擎升级:Nashorn
- Java的动态编译器
1. JDK 和 JRE 目录结构的改变
JDK 8 的目录结构
JDK_HOME
|--bin
|--include
|--jre
|--|--bin
|--|--lib
|--lib
目录 | 描述 |
---|---|
bin | 包含命令行开发和调试工具,如 javac , jar 和 javadoc |
include | 包含在编译本地代码时使用的 C/C++ 头文件 |
lib | 包含 JDK 工具的几个 JAR 和其他类型的文件。它有一个 tools.jar 文件,其中包含 javac 编译器的 Java 类 |
jre/bin | 包含基本命令,如 java 命令。在 Windows 平台上,它包含系统的运行时动态链接库( DLL ) |
jre/lib | 包含用户可编辑的配置文件,如 .properties 和 .policy 文件。包含几个 JAR 。 rt.jar 文件包含运行时的 Java 类和资源文件 |
JDK 9 的目录结构
JDK_HOME
|--bin
|--conf
|--include
|--jmods
|--legal
|--lib
没有名为 jre 的子目录
目录 | 描述 |
---|---|
bin | 包含所有命令。在 Windows 平台上,它继续包含系统的运行时动态链接库 |
conf | 包含用户可编辑的配置文件,例如以前位于 jre\lib 目录中的 .properties 和 .policy 文件 |
include | 包含要在以前编译本地代码时使用的 C/C++ 头文件。它只存在于 JDK 中 |
jmods | 包含 JMoD 格式的平台模块。创建自定义运行时映像时需要它。它只存在于 JDK 中 |
legal | 包含法律声明 |
lib | 包含非 Windows 平台上的动态链接本地库。其子目录和文件不应由开发人员直接编辑或使用 |
jdk 17 也是这样的目录结构
2. 模块化系统: Jigsaw -> Modularity
- 谈到 Java 9 大家往往第一个想到的就是 Jigsaw 项目。众所周知, Java 已经发展超过 20 年( 95 年最初发布), Java 和相关生态在不断丰富的同时也越来越暴露出一些问题:
- Java 运行环境的膨胀和臃肿。每次 JVM 启动的时候,至少会有 30 - 60MB 的内存加载,主要原因是 JVM 需要加载
rt.jar
,不管其中的类是否被 classloader 加载,第一步整个 jar 都会被 JVM 加载到内存当中去(而模块化可以根据模块的需要加载程序运行需要的 class - 当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的增长。不同版本的类库交叉依赖导致让人头疼的问题,这些都阻碍了 Java 开发和运行效率的提升。
- 很难真正地对代码进行封装,而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到,这样就会导致无意中使用了并不想被公开访问的 API
- Java 运行环境的膨胀和臃肿。每次 JVM 启动的时候,至少会有 30 - 60MB 的内存加载,主要原因是 JVM 需要加载
- 本质上讲,模块( module )的概念,其实就是 package 外再裹一层,也就是说,用模块来管理各个 package ,通过声明某个 package 暴露,不声明默认就是隐藏。因此,模块化使得代码组织上更安全,因为它可以指定哪些部分可以暴露,哪些部分隐藏。
- 实现目标:
- 模块化的主要目的在于减少内存的开销
- 只须必要模块,而非全部 JDK 模块,可简化各种类库和大型应用的开发和维护
- 改进 Java Se 平台,使其可以适应不同大小的计算设备
- 改进其安全性,可维护性,提高性能
示例
jdk901 模块下 :
module-info.java
module jdk901 {
exports beans;
}
MyBean1.java
package beans;
public class MyBean1 {
private Integer id;
public MyBean1(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "MyBean1{" + "id=" + id + '}';
}
}
jdk902 模块下 :
jdk902
对 jdk901
有依赖关系,类似引入了一个 jar 包
module-info.java
module jdk902 {
requires jdk901;
}
MyMain.java
package main;
import beans.MyBean1;
public class MyMain {
public static void main(String[] args) {
MyBean1 myBean1 = new MyBean1(1);
System.out.println(myBean1);
}
}
3. Java 的 REPL 工具: jShell 命令
- 产生背景
- 像 Python 和 Scala 之类的语言早就有交互式编程环境 REPL ( read - evaluate - print - loop )了,以交互式的方式对语句和表达式进行求值。开发者只需要输入一些代码,就可以在编译前获得对程序的反馈。而之前的 Java 版本要想执行代码,必须创建文件、声明类、提供测试方法方可实现。
- 设计理念
- 即写即得、快速运行
- 实现目标
- Java 9 中终于拥有了 REPL 工具: jShell
- 让 Java 可以像脚本语言一样运行,从控制台启动 jShell ,利用 jShell 在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行 Java 的代码,而无需创建 Java 文件,无需跟人解释
public static void main(String[] args)
这句废话。
- 让 Java 可以像脚本语言一样运行,从控制台启动 jShell ,利用 jShell 在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行 Java 的代码,而无需创建 Java 文件,无需跟人解释
- jShell 也可以从文件中加载语句或者将语句保存到文件中。
- jShell 也可以使用 tab 键进行自动补全和自动添加分号
- Java 9 中终于拥有了 REPL 工具: jShell
使用 bin\jShell.exe
4. 语法改进:接的私有方法
Java 8 中规定接口中的方法除了抽象方法之外,还可以定义静态方法和默认的方法。一定程度上,扩展了接口的功能,此时的接口更像是个抽象类。
在 Java 9 中,接口更加的灵活和强大,连方法的访问权限修饰符都可以声明为 private
的了,此时方法将不会成为你对外暴露的 API 的一部分。
示例
接口:
package beans;
public interface MyInterface {
void publicMethod();
static void staticMethod() {
System.out.println("接口的静态方法");
}
default void defaultMethod() {
System.out.println("接口的默认方法");
privateMethod();
}
private void privateMethod() {
System.out.println("接口的私有方法");
}
}
实现类:
package beans;
public class MyInterfaceImpl implements MyInterface {
@Override
public void publicMethod() {
System.out.println("实现类重写接口的公共方法");
}
@Override
public void defaultMethod() {
System.out.println("实现类重写接口的默认方法");
}
public static void main(String[] args) {
// 接口的静态方法只能接口调用,实现类不能调用
MyInterface.staticMethod();
// MyInterfaceImpl.staticMethod();
MyInterfaceImpl myInterfaceImpl = new MyInterfaceImpl();
myInterfaceImpl.defaultMethod();
}
}
5. 语法改进:钻石操作符使用升级
我们将能够与匿名实现类共同使用钻石操作符( diamond operator )
在 Java 8 中如下的操作是会报错的:
编译报错信息:Cannot use "<>" with anonymous inner classes
Comparator<Object> comparator = new Comparator<>() {
@Override
public int compare(Object o1, Object o2) {
return 0;
}
};
6. 语法改进: try 语句
Java 8 中,可以实现资源的自动关闭,但是要求执行后必须关闭的所有资源必须在 try 子句中初始化,否则编译不通过。
try (InputStreamReader inputStreamReader = new InputStreamReader(System.in)) {
// ...
} catch (IOException e) {
e.printStackTrace();
}
Java 9 中,用资源语句编写 try 将更容易,我们可以在 try 子句中使用已经初始化过的资源,此时的资源是 final
的:
InputStreamReader reader = new InputStreamReader(System.in);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try (reader; writer) {
// reader 、writer 都是 final 的,不可以再被赋值
// reader = null;
} catch (IOException e) {
e.printStackTrace();
}
在 Java 9 中,需要自动关闭的资源的实例化可以放在 try 的一对小括号外面
7. String 存储结构变更
结论: String
再也不用 char[]
来存储啦,改成了 byte[]
加上编码标记,节约了一些空间。
private final byte[] value;
StringBuffer
和 StringBuilder
进行了相同的变更
8. 集合工厂方法:快速创建只读集合
Java 8 要创建一个 只读、不可改变的 集合,必须构造和分配它,然后添加元素,最后包装成一个不可修改的集合。
List<String> nameList = new ArrayList<>();
nameList.add("A");
nameList.add("B");
nameList.add("C");
List<String> unmodifiableList = Collections.unmodifiableList(nameList);
Java 9 因此引入了方便的 of
方法,创建只读、不可修改的集合
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
Set<Integer> set1 = Set.of(1, 2, 3);
Map<Integer, String> map1 = Map.of(1, "A", 2, "B", 3, "C");
Map<Integer, String> map2 = Map.ofEntries(Map.entry(1, "A"), Map.entry(2, "B"), Map.entry(3, "C"));
9. InputStream 加强
InputStream
终于有了一个非常有用的方法: transferTo
,可以用来将数据直接传输到 OutputStream
,这是在处理原始数据流时非常常见的一种用法
ClassLoader cl = MyMain.class.getClassLoader();
try (InputStream is = cl.getResourceAsStream("hello. txt"); OutputStream os = new FileOutputStream("src\\hello1.txt")) {
is.transferTo(os); // 把输入流中的所有数据直接自动地复制到输出流中
} catch (IOException e) {
e.printStackTrace();
}
10. 增强的 Stream API
- Java 的 Steam API 是 Java 标准库最好的改进之一,让开发者能够快速运算,从而能够有效的利用数据并行计算。 Java 8 提供的 Steam 能够利用多核架构实现声明式的数据处理
- 在 Java 9 中, Stream API 变得更好, Stream 接口中添加了 4 个新的方法:
takeWhile
,dropWhile
,ofNullable
,还有iterate
方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代 - 除了对 Stream 本身的扩展,
Optional
和 Stream 之间的结合也得到了改进。现在可以通过Optional
的新方法stream()
将一个Optional
对象转换为一个(可能是空的) Stream 对象。
takeWhile()
用于从 Stream 中获取一部分数据,接收一个 Predicate
来进行选择。takeWhile
返回从开头开始的按照指定规则尽量多的元素。dropWhile
刚好相反,返回剩余的元素。
List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 90, 73, 67, 88);
list.stream().takeWhile(x -> x < 50).forEach(System.out::println); // 45, 43
System.out.println("============");
list.stream().dropWhile(x -> x < 50).forEach(System.out::println); // 76, 87, 42, 77, 90, 73, 67, 88
System.out.println("============");
list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
list.stream().takeWhile(x -> x < 5).forEach(System.out::println); // 1, 2, 3, 4
System.out.println("============");
list.stream().dropWhile(x -> x < 5).forEach(System.out::println); // 5, 6, 7, 8
ofnullable()
的使用,Java 8 中 Stream
不能完全为 null
,否则会报空指针异常。而 Java 9 中的 ofnullable
方法允许我们创建一个单元素 Stream
,可以包含一个非空元素,也可以创建一个空 Stream
。
iterate
方法的新重载方法,可以让你提供一个 Predicate
(判断条件)来指定什么时候结束迭代。
// 原先的控制终止方式
Stream.iterate(1, i -> i + 1).limit(5).forEach(System.out::println);
// 现在的控制终止方式
Stream.iterate(1, i -> i <= 5, i -> i + 1).forEach(System.out::println);
11. Optional
获取 Stream 的方法
Optional
类中 stream()
的使用
List<Integer> list = List.of(1, 2, 3, 4, 5);
Optional<List<Integer>> optional = Optional.of(list);
Stream<List<Integer>> stream = optional.stream();
stream.flatMap(l -> l.stream()).forEach(System.out::println);
optional.stream().flatMap(Collection::stream).forEach(System.out::println);
12. JavaScript 引擎升级: Nashorn
- Nashorn 项目在 JDK 9 中得到改进,它为 Java 提供轻量级的 JavaScript 运行时。 Nashorn 项目跟随 Netscape 的 Rhino 项目,目的是为了在 Java 中实现一个高性能但轻量级的 JavaScript 运行时。 Nashorn 项目使得 Java 应用能够嵌入 JavaScript 。它在 JDK 8 中为 Java 提供一个 JavaScript 引擎。
- JDK 9 包含一个用来解析 Nashorn 的 ECMAScript 语法树的 API 。这个 API 使得 IDE 和服务端框架不需要依赖 Nashorn 项目的内部实现类,就能够分析 ECMAScript 代码。
- JDK 11 被废弃
完整的 JavaScript 实现包含三个部分:
- ECMAScript (描述该语言的语法和基本对象)
- 文档对象模型(描述处理网页内容的方法和接口)
- 浏览器对象模型(描述与浏览器进行交互的方法和实现)
Java 10
概述
- 2018 年 3 月 21 日, Oracle 官方宣布 Java 10 正式发布
- 需要注意的是 Java 9 和 Java 10 都不是 LTS ( Long- Term-support )版本。和过去的 Java 大版本升级不同,这两个只有半年左右的开发和维护期。而未来的 Java 11 ,也就是 18.9 LTS ,才是 Java 8 之后第一个 LTS 版本
- JDK 10 一共定义了 109 个新特性,其中包含 12 个 JEP (对于程序员来讲,真正的新特性其实就一个,局部变量类型推断),还有一些新 API 和 JVM 规范以及 Java 语言规范上的改动
- JDK 10 的 12 个 JEP ( JDK Enhancement Proposal 特性加强提议)参阅 官方文档
- 286 : Local-variable Type Inference 局部变量类型推断
- 296 : Consolidate the JDK Forest into a Single Repository JDK 库的合并
- 304 : Garbage-Collector Interface 统一的垃圾回收接口
- 307 : Parallel Full GC for G1 为 G1 提供并行的 Full GC
- 310 : Application Class-Data Sharing 应用程序类数据( AppCDS )共享
- 312 : Thread-Local Handshakes Threadloca 握手交互
- 313 : Remove the Native-Header Generation Tool ( Java )移除 JDK 中附带的 javah 工具
- 314 : Additional Unicode Language-Tag Extensions 使用附加的 Unicode 语言标记扩展
- 316 : Heap Allocation on Alternative Memory Devices 能将堆内存占用分配给用户指定的备用内存设备
- 317 : Experimental Java- Based JIT Compiler 使用基于 Java 的 JIT 编译器
- 319 : Root Certificates 根证书
- 322 : Time-Based Release Versioning 基于时间的发布版本
1. 局部变量类型推断
概述
-
产生背景:开发者经常抱怨 Java 中引用代码的程度。局部变量的显式类型声明,常常被认为是不必须的,给一个好听的名字经常可以很清楚的表达出下面应该怎样继续
-
好处:减少了啰嗦和形式的代码,避免了信息冗余,而且对齐了变量名,更容易阅
-
举例如下:
-
场景一:类实例化时作为 Java 开发者,在声明一个变量时,我们总是习惯了敲打两次变量类型,第一次用于声明变量类型,第二次用于构造器。
LinkedHashSet<Integer> set = new LinkedHashSet<>();
-
场景二:返回值类型含复杂泛型结构,变量的声明类型书写复杂且较长,尤其是加上泛型的使用
Map<Integer, String> map = Map.of(1, "A", 2, "B", 3, "C"); Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
-
场景三:我们也经常声明一种变量,它只会被使用一次,而且是用在下一行代码中,比如:
URL url = new URL("http://www.atguigu.com"); URLConnection connection = url.openConnection(); Reader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
尽管 IDE 可以帮我们自动完成这些代码,但当变量总是跳来跳去的时候,可读性还是会受到影响,因为变量类型的名称由各种不同长度的字符组成。而且,有时候开发人员会尽力避免声明中间变量,因为太多的类型声明只会分散注意力,不会带来额外的好处。
-
适用的几种情况
// 声明变量时,根据赋值,推断变量类型
var num = 10;
var list = new ArrayList<String>();
list.add("A");
// 遍历操作
for (var v : list) {
System.out.println(v);
System.out.println(v.getClass());
}
// 普通遍历操作
for (var i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
不适用的几种情况
// var v1; // 没有初始化
// var v1 = null; // 初始值为 null
// var v2 = System.out::println; // 方法引用
// var v3 = () -> Math.random(); // lambda 表达式
// var v4 = {1, 2, 3, 4}; // 数组静态初始化
var v5 = new int[]{1, 2, 3, 4}; // 正确
- 没有初始化的局部变量声明
- 方法的参数类型
- 方法的返回类型
- 构造器的参数类型
- 属性
- catch 块
工作原理
在处理 var 时,编译器先是査看表达式右边部分,并根据右边变量值的类型进行推断,作为左边变量的类型,然后将该类型写入字节码当中。
注意
var
不是一个关键字- 你不需要担心变量名或方法名会与 var 发生冲突,因为 var 实际上并不是一个关键字,而是一个类型名,只有在编译器需要知道类型的地方才需要用到它。除此之外,它就是一个普通合法的标识符。也就是说,除了不能用它作为类名,其他的都可以,但极少人会用它作为类名。
- 这不是 JavaScript
- 首先我要说明的是, var 并不会改变 Java 是一门静态类型语言的事实。编译器负责推断出类型,并把结果写入字节码文件,就好像是开发人员自己敲入类型一样。下面是使用 Intellij(实际上是 Fernflower 的反编译器)反编译器反编译出的代码
var url = new URL("http://www.atguigu.com");
var connection = url.openConnection();
var reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
System.out.println(reader);
反编译出的代码:
URL url = new URL("http://www.atguigu.com");
URLConnection connection = url.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
System.out.println(reader);
2. 集合新增创建不可变集合的方法
自 Java 9 开始, JDK 里面为集合( List / Set / Map )都添加了 of
( JDK 9 新增)和 copyOf
( JDK 10 新增)方法,它们两个都用来创建不可变的集合,来看下它们的使用和区别。
//示例1
var list1 = List.of("Java", "Python", "C");
var copy1 = List.copyOf(list1);
System.out.println(list1 == copy1); // true
// 示例2
var list2 = new ArrayList<String>();
var copy2 = List.copyOf(list2);
System.out.println(list2 == copy2); // false
// 示例1和2代码基本一致,为什么一个为true,一个为 false?
// 通过查看 copyOf 方法的源码:
// 1. 如果入参本身是只读集合,直接返回
// 2. 如果入参不是只读集合,使用 of 方法新生成一个只读集合返回
Java 11
北京时间 2018 年 9 月 26 日,Oracle 官方宣布 Java 11 正式发布。这是 Java 大版本周期变化后的第一个长期支持版本,非常值得关注。从官网即可下载,最新发布的 Java 11 将带来 ZGC 、Http Client 等重要特性,一共包含 17 个 JEP ( JDK Enhancement Proposals , JDK 增强提案)。其实,总共更新不止 17 个,只是我们更关注如下的 17 个 JEP 更新:
- 181: Nest-based Access Control (基于嵌套的访问控制)
- 309: Dynamic Class-file Constants(动态的类文件常量)
- 315: Improve Aarch64 Intrinsics(改进 Aarch64 Intrinsics)
- 318: Epsilon: A No-op Garbage Collector(Epsilon 垃圾回收器,又被称为 No-Op(无操作)回收器)
- 320: Remove the Java EE and CORBA Modules(移除 Java EE 和 CORBA 模块,JavaFX 也已被移除)
- 321: HTTP Client (Standard)
- 323: Local-Variable Syntax for Lambda Parameters(用于 Lambda 参数的局部变量语法)
- 324: Key Agreement with Curve25519 and Curve448(采用 Curve25519 和 Curve448 算法实现的密钥协议)
- 327: Unicode 10
- 328: Flight Recorder(飞行记录仪)
- 329: ChaCha20 and Poly1305 Cryptographic Algorithms(实现 ChaCha20 和 Poly1305 加密算法)
- 330: Launch Single-file Source-Code Programs(启动单个 Java 源代码文件的程序)
- 331: Low-Overhead Heap Profiling(低开销的堆分配采样方法)
- 332: Transport Layer Security (TLS) 1.3(对 TLS 1.3 的支持)
- 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)(ZGC:可伸缩的低延迟垃圾回收器,处于实验性阶段)
- 335: Deprecate the Nashorn Java Script Engine(弃用 Nashorn JavaScript 引擎)
- 336: Deprecate the Pack200 Tools and API(启用 Pack200 工具及其 API )
JDK 11 将是一个企业不可忽视的版本。从时间节点来看, JDK 11 的发布正好处在 JDK 8 免费更新到期的前夕,同时 JDK 9 、 10 也陆续成为“历史版本”,下面是 Oracle JDK 支持路线图:
JDK 11 是一个长期支持版本( LTS , Long-Term-Support )
- 对于企业来说,选择 11 将意味着长期的、可靠的、可预测的技术路线图。其中免费的 OpenJDK 11 确定将得到 OpenJDK 社区的长期支持, LTS 版本将是可以放心选择的版本。
- 从 JVM GC 的角度, JDK 11 引入了两种新的 GC ,其中包括也许是划时代意义的 ZGC ,虽然其目前还是实验特性,但是从能力上来看,这是 JDK 的一个巨大突破,为特定生产环境的苛刻需求提供了一个可能的选择。例如,对部分企业核心存储等产品,如果能够保证不超过 10ms 的 GC 暂停,可靠性会上一个大的台阶,这是过去我们进行 GC 调优几乎做不到的,是能与不能的问题。
按照官方的说法,新的发布周期会严格遵循时间点,将于每年的 3 月份和 9 月份发布。所以 Java 11 的版本号是 18.9 ( LTS )。
不过与 Java 9 和 Java 10 这两个被称为“功能性的版本”不同(两者均只提供半年的技术支持), Java 11 不仅提供了长期支持服务,还将作为 Java 平台的参考实现。 Oracle 直到 2023 年 9 月都会为 Java 11 提供技术支持,而补丁和安全警告等扩展支持将持续到 2026 年。
新的长期支持版本每三年发布一次,根据后续的发布计划,下个长期支持版 Java 17 将于 2021 年发布。
1. 新增了一系列 String 字符串处理方法
方法 | 描述 |
---|---|
isBlack |
判断字符串是否为空白 |
strip |
去除首尾空白 |
stripTrailing |
去除尾部空格 |
stripLeading |
去除首部空格 |
repeat |
复制字符串 |
lines |
返回从此字符串中提取的行流,由行终止符分隔 |
System.out.println(" ".isBlank()); // true
System.out.println(" Java stack ".strip()); // 去除首尾空格
System.out.println(" Java stack ".stripTrailing()); // 去除尾空格
System.out.println(" Java stack ".stripLeading()); // 去除首空格
System.out.println("Java".repeat(3)); // JavaJavaJava
Stream<String> lines = "A\nb\nC".lines();
System.out.println(lines.count()); // 3
2. Optional
加强
Optional
也增加了几个非常酷的方法,现在可以很方便的将一个 Optional
转换成一个 Stream
,或者当一个空 Optional
时给它一个替代的。
新增方法 | 描述 | 新增的版本 |
---|---|---|
isEmpty |
判断 value 是否为空 | 11 |
ifPresentOrElse |
value 非空,执行参数1功能; value 为空,执行参数2功能 |
9 |
or |
value 非空,返回对应的 Optional value 为空,返回形参封装的 Optional |
9 |
stream |
value 非空,返回仅包含此 value 的 Stream 否则,返回一个空的 Stream |
9 |
orElseThrow |
value 非空,返回 value 否则,抛出异常 NoSuchElementException |
10 |
3. 局部变量类型推断升级
在 var
上添加注解的语法格式,在 JDK 10 中是不能实现的。在 JDK 11 加入了这样的语法。
4. 全新的 HTTP 客户端 API
- HTTP ,用于传输网页的协议,早在 1997 年就被采用在目前的 1.1 版本中。直到 2015 年, HTTP/2 才成为标准。
- 1991 年,HTTP/0.9
- 1996 年,HTTP/1.0
- 1999 年,HTTP/1.1
- 2015 年,HTTP/2
- HTTP/1.1 和 HTTP/2 的主要区别是如何在客户端和服务器之间构建和传输数据。 HTTP/1.1 依赖于请求 / 响应周期。 HTTP/2 允许服务器 push 数据:它可以发送比客户端请求更多的数据。这使得它可以优先处理并发送对于首先加载网页至关重要的数据。
- 这是 Java 9 开始引入的一个处理 HTTP 请求的的 HTTP Client API ,该 API 支持同步和异步,而在 Java 11 中已经为正式可用状态,你可以在
java.net
包中找到这个 APl - 它将 替代仅适用于 blocking 模式的
HttpURLConnection
(HttpURLConnection
是在 HTTP/1.0 的时代创建的,并使用了协议无关的方法),并提供对 WebSocket 和 HTTP/2 的支持。
示例
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:8080/test/")).build();
HttpResponse.BodyHandler<String> responseBodyHandler = HttpResponse.BodyHandlers.ofString();
HttpResponse<String> response = client.send(request, responseBodyHandler);
String body = response.body();
System.out.println(body);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:8080/test/")).build();
HttpResponse.BodyHandler<String> responseBodyHandler = HttpResponse.BodyHandlers.ofString();
CompletableFuture<HttpResponse<String>> sendAsync = client.sendAsync(request, responseBodyHandler);
sendAsync.thenApply(t -> t.body()).thenAccept(System.out::println);
// HttpResponse<String> response = sendAsync.get();
// String body = response.body();
// System.out.println(body);
5. 更简化的编译运行程序
看下面的代码。
// 编译
javac Javastack.java
// 运行
java Javastack
在我们的认知里面,要运行一个 Java 源代码必须先编译,再运行,两步执行动作。
而在未来的 Java 11 版本中,通过一个 java
命令就直接搞定了,如以下所示
java Javastack.java
一个命令编译运行源代码的注意点:
- 执行源文件中的第一个类,第一个类必须包含主方法。
- 并且不可以使用其它源文件中的自定义类,本文件中的自定义类是可以使用的。
6. 废弃 Nashorn 引擎
废除 Nashorn JavaScript 引擎,在后续版本准备移除掉,有需要的可以考虑使用 GraalVM
7. ZGC
- GC 是 Java 主要优势之一。然而,当 GC 停顿太长,就会开始影响应用的响应时间。消除或者减少 GC 停顿时长, Java 将对更广泛的应用场景是一个更有吸引力的平台。此外,现代系统中可用内存不断增长,用户和程序员希望 JVM 能够以高效的方式充分利用这些内存,并且无需长时间的 GC 暂停时间。
- ZGC , A Scalable Low-Latency Garbage Collector ( Experimental ) ZGC ,这应该是 JDK 11 最为瞩目的特性,没有之一。但是后面带了 Experimental ,说明这还不建议用到生产环境。
- ZGC 是一个并发,基于 region ,压缩型的垃圾收集器,只有 root 扫描阶段会 STW ( stop the world ),因此 GC 停顿时间不会随着堆的增长和存活对象的增长而变长。
- 优势
- GC 暂停时间不会超过 10ms
- 既能处理几百兆的小堆,也能处理几个 T 的大堆( OMG )
- 和 G1 相比,应用吞吐能力不会下降超过 15%
- 为未来的 GC 功能和利用 colord 指针以及 Load barriers 优化奠定基础
- 初始只支持 64 位系统
- ZGC 的设计目标是:支持 TB 级内存容量,暂停时间低(< 10ms ),对整个程序吞吐量的影响小于 15% 。将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于 DRAM 和冷对象置于 NVMe 闪存)或压缩堆。
8. 其它新特性
- Unicode 10
- Deprecate the Pack200 Tools and API
- 新的 Epsilon 垃圾收集器
- 完全支持 Linux 容器(包括 Docker )
- 支持 G1 上的并行完全垃圾收集
- 最新的 HTTPS 安全协议 TLS1.3
- Java Flight Recorder
在当前 JDK 中看不到什么?
一个标准化和轻量级的 JSON API
一个标准化和轻量级的 JSON API 被许多 Java 开发人员所青睐。但是由于资金问题无法在 Java 当前版本中见到,但并不会削减掉。 Java 平台首席架构师 Mark Reinhold 在 JDK 9 邮件列中说:”这个 JEP 将是平台上的一个有用的补充,但是在计划中,它并不像 Oracle 资助的其他功能那么重要,可能会重新考虑 JDK 10 或更高版本中实现。“
新的货币 API
-
对许多应用而言货币价值都是一个关键的特性,但 JDK 对此却几乎没有任何支持。严格来讲,现有的
java.util.Currency
类只是代表了当前 lSO 4217 货币的一个数据结构,但并没有关联的值或者自定义货币。 JDK 对货币的运算及转换也没有内建的支持,更别说有一个能够代表货币值的标准类型了。 -
此前, Oracle 公布的 JSR 354 定义了一套新的 Java 货币 API : JavaMoney ,计划会在 Java 9 中正式引入。但是目前没有出现在 JDK 新特性中
-
不过,如果你用的是 Maven 的话,可以做如下的添加,即可使用相关的 API 处理货币
<dependency> <groupId>org.javamoney</groupId> <artifactId>moneta</artifactId> </dependency>
展望
- 随着云计算和 AI 等技术浪潮,当前的计算模式和场景正在发生翻天覆地的变化,不仅对 Java 的发展速度提出了更高要求,也深刻影响着 Java 技术的发展方向。传统的大型企业或互联网应用,正在被云端、容器化应用、模块化的微服务甚至是函数( Faas , Function-as-a-Service )所替代。
- Java 虽然标榜面向对象编程,却毫不顾忌的加入面向接口编程思想,又扯出匿名对象之概念,每增加一个新的东西,都是对 Java 的根本所在的面向对象思想的次冲击。反观 Python ,抓住面向对象的本质,又能在函数编程思想方面游刃有余。 Java 对标 C/C++ ,以抛掉内存管理为卖点,却又陷入了 JVM 优化的噩梦。选择比努力更重要,选择 Java 的人更需要对它有更清晰的认识。
- Java 需要在新的计算场景下,改进开发效率。这话说的有点笼统,我谈一些自己的体会, Java 代码虽然进行了一些类型推断等改进,更易用的集合 API 等,但仍然给开发者留下了过于刻板、形式主义的印象,这是一个长期的改进方向。
Java 12
概述
美国当地时间 2019 年 3 月 19 日,也就是北京时间 20 号 Java 12 正式发布了!
总共有 8 个新的 JEP ( JDK Enhancement Proposals )
- 189:Shenandoah:A Low-Pause-Time Garbage Collector(Experimental)
- 低暂停时间的GC
- 230:Microbenchmark Suite
- 微基准测试套件
- 325:Switch Expressions(Preview)
- switch表达式
- 334:JVM Constants API
- JVM常量API
- 340:One AArch64 Port,Not Two
- 只保留一个AArch64实现
- 341:Default CDS Archives
- 默认类数据共享归档文件
- 344:Abortable Mixed Collections for G1
- 可中止的G1 Mixed GC
- 346:Promptly Return Unused Committed Memory from G1
- G1及时返回未使用的已分配内存
1. switch 表达式(预览)
1.1. 传统 switch 的弊端
传统的 switch 声明语句( switch statement )在使用中有一些问题:
-
匹配是自上而下的,如果忘记写 break , 后面的 case 语句不论匹配与否都会执行;
-
所有的 case 语句共用一个块范围,在不同的 case 语句定义的变量名不能重复;
-
不能在一个 case 里写多个执行结果一致的条件;
-
整个 switch 不能作为表达式返回值;
Java 12 将会对 switch 声明语句进行扩展,可将其作为增强版的 switch 语句或称为 "switch 表达式" 来写出更加简化的代码。
1.2. 何为预览语言?
Switch 表达式也是作为预览语言功能的第一个语言改动被引入新版 Java 中来的,预览语言功能的想法是在 2018 年初被引入 Java 中的,本质上讲,这是一种引入新特性的测试版的方法。通过这种方式,能够根据用户反馈进行升级、更改,在极端情况下,如果没有被很好的接纳,则可以完全删除该功能。预览功能的关键在于它们没有被包含在 Java SE 规范中。
1.3. 语法详解
扩展的 switch 语句,不仅可以作为语句( statement ),还可以作为表达式( expression ),并且两种写法都可以使用传统的 switch 语法,或者使用简化的“ case L - >”模式匹配语法作用于不同范围并控制执行流。这些更改将简化日常编码工作,并为 switch 中的模式匹配( JEP 305 )做好准备。
- 使用 Java 12 中 switch 表达式的写法,省去了 break 语句,避免了因少写 break 而出错。
- 同时将多个 case 合并到一行,显得简洁、清晰也更加优雅的表达逻辑分支,其具体写法就是将之前的 case 语句表成了:
case L - >
,即如果条件匹配case L
,则执行标签右侧的代码 ,同时标签右侧的代码段只能是表达式、代码块或 throw 语句。 - 为了保持兼容性, case 条件语句中依然可以使用字符 : ,这时 fall-through 规则依然有效的,即不能省略原有的 break 语句,但是同一个 switch 结构里不能混用
->
和:
,否则会有编译错误。并且简化后的 switch 代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个 switch 结构,也不用根据不同的判断条件来给变量赋值。
1.4. 代码举例
Java 12 之前
public class SwitchTest {
public static void main(String[] args) {
int numberOfLetters;
Fruit fruit = Fruit.APPLE;
switch (fruit) {
case PEAR:
// int number = 10;
numberOfLetters = 4;
break;
//case 穿透
case APPLE:
case GRAPE:
case MANGO:
// int number = 20;
numberOfLetters = 5;
break;
//错误的语法
// case APPLE,GRAPE,MANGO:numberOfLetters = 5;
case ORANGE:
case PAPAYA:
numberOfLetters = 6;
break;
default:
throw new IllegalStateException("No Such Fruit:" + fruit);
}
System.out.println(numberOfLetters);
}
}
enum Fruit {
PEAR, APPLE, GRAPE, MANGO, ORANGE, PAPAYA;
}
Java 12
如果有编码经验,你一定知道, switch 语句如果漏写了一个 break ,那么逻辑往往就跑偏了,这种方式既繁琐,又容易出错。如果换成 switch 表达式, Pattern Matching 机制能够自然地保证只有单一路径会被执行:
Fruit fruit = Fruit.APPLE;
switch (fruit) {
case PEAR -> System.out.println("4");
case APPLE, GRAPE, MANGO -> System.out.println("5");
case ORANGE, PAPAYA -> System.out.println("6");
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
}
更进一步,下面的表达式,为我们提供了优雅地表达特定场合计算逻辑的方式:
int numberOfLetters;
Fruit fruit = Fruit.APPLE;
numberOfLetters = switch (fruit) {
case PEAR -> 4;
case APPLE, GRAPE, MANGO -> 5;
case ORANGE, PAPAYA -> 6;
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
};
System.out.println(numberOfLetters);
1.5. 使用总结
这个语法如果做过 Android 开发的不会陌生,因为 Kotlin 家的 when 表达式就是这么干的!
Switch Expressions 或者说起相关的 Pattern Matching 特性,为我们勾勒出了 Java 语法进化的一个趋势,将开发者从复杂繁琐的低层次抽象中逐渐解放出来,以更高层次更优雅的抽象,既降低代码量,又避免意外编程错误的出现,进而提高代码质量和开发效率。
1.6. 展望
Java 11 以及之前版本中, switch 表达式支持下面类型: byte 、 char 、 short 、 int 、 Byte 、 Character 、 Short 、Integer 、 enum 、 String ,在未来的某个 Java 版本有可能会允许支持 float 、 double 和 long (以及对应类型的包装类型)
2. Shenandoah GC :低停顿时间的 GC (预览)
2.1. 背景和设计思路
Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针对 JVM 上的内存收回实现低停顿的需求。该设计将与应用程序线程并发,通过交换 CPU 并发周期和空间以改善停顿时间,使得垃圾回收器执行线程能够在 Java 线程运行时进行堆压缩,并且标记和整理能够同时进行,因此避免了在大多数 JVM 垃圾收集器中所遇到的问题。
据 Red Hat 研发 Shenandoah 团队对外宣称, Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB ,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。
与其他 Pauseless GC 类似, Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms ,暂停与堆大小无关等。
这是一个实验性功能,不包含在默认( Oracle )的 OpenJDK 版本中。
2.2. 补充: STW
Stop-the-World ,简称 STW ,指的是 GC 事件发生过程中,停止所有的应用程序线程的执行。就像警察办案,需要清场一样
垃圾回收器的任务是识别和回收垃圾对象进行内存清理。为了让垃圾回收器可以正常且高效地执行,大部分情况下会要求系统进入一个停顿的状态。停顿的目的是终止所有应用程序的执行,只有这样,系统中才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于垃圾回收器更好地标记垃圾对象。因此,在垃圾回收时,都会产生应用程序的停顿。停顿产生时整个应用程序会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW 。
如果 Stop-the-World 出现在新生代的 Minor GC 中时, 由于新生代的内存空间通常都比较小, 所以暂停时间也在可接受的合理范围之内,不过一旦出现在老年代的 Full GC 中时,程序的工作线程被暂停的时间将会更久。简单来说,内存空间越大,执行 Full GC 的时间就会越久, 相对的工作线程被暂停的时间也就会更长。
到目前为止,哪怕是 G1 也不能完全避免 Stop-the-World 情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高, 尽可能地缩短了暂停时间。
2.3. 补充:垃圾收集器的分类
由于 JDK 的版本处于高速迭代过程中,因此 Java 发展至今已经衍生了众多的 GC 版本。
从不同角度分析垃圾收集器,可以将 GC 分为不同的类型:
- 按线程数分,可以分为串行垃圾回收器和并行垃圾回收器
- 串行回收指的是在同一时间段内只允许一件事情发生,简单来说,当多个 CPU 可用时,也只能有一个 CPU 用于执行垃圾回收操作,井且在执行垃圾回收时,程序中的工作线程将会被暂停,当垃圾收集工作完成后才会恢复之前被暂停的工作线程,这就是串行回收。
- 和串行回收相反,并行收集可以运用多个 CPU 同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用了“ Stop-the-World ”机制和复制算法。
- 按照工作模式分,可以分为并发式回收器和独占式垃圾回收器。
- 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。
- 独占式垃圾回收器( Stop the world )一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束。
- 按碎片处理方式可分为压缩式垃圾回收器和非压缩式垃圾回收器。
- 压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片。
- 非压缩式的垃圾回收器不进行这步操作。
- 按工作的内存区间,又可分为年轻代垃圾回收器和老年代垃圾回收器。
2.4. 补充:如何评估一款 GC 的性能
- 吞吐量:程序的运行时间(程序的运行时间+内存回收的时间)。
- 垃圾收集开销:吞吐量的补数,垃圾收集器所占时间与总时间的比例。
- 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
- 收集频率:相对于应用程序的执行,收集操作发生的频率。
- 堆空间: Java 堆区所占的内存大小。
- 快速: 一个对象从诞生到被回收所经历的时间。
需要注意的是, 垃圾收集器中吞吐量和低延迟这两个目标本身是相互矛盾的,因为如果选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致 GC 需要更长的暂停时间来执行内存回收。相反的,如果选择以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,也只能频繁地执行内存回收,但这又引起了年轻代内存的缩减和导致程序吞吐量的下降。
2.5 工作原理
略
2.6. 信息延展
也许了解 Shenandoah GC 的人比较少,业界声音比较响亮的是 Oracle 在 JDK11 中开源出来的 ZGC,或者商业版本的 Azul C4(Continuously Concurrent Compacting Collector)。但是,至少目前我认为,其实际意义大于后两者,因为:
- 使用 ZGC 的最低门槛是升级到 JDK11 ,对很多团队来说,这种版本的跳跃并不是非常低成本的事情,更何况是尚不清楚 ZGC 在自身业务场景中的实际表现如何。
- 而 C4 ,毕竟是土豪们的选择,现实情况是,有多少公司连个几十块钱的 License 都不舍得。
- 而 Shenandoah GC 可是有稳定的 JDK8u 版本发布的。据了解,已经有个别公司在 HBase 等高实时性产品中实践许久。
- ZGC 也是面向 low-pause-time 的垃圾收集器,不过 ZGC 是基于 colored pointers 来实现,而 Shenandoah GC 是基于 brooks pointers 来实现。
需要了解,不是唯有 GC 停顿可能导致常规应用程序响应时间比较长。具有较长的 GC 停顿时间会导致系统响应慢的问题,但响应时间慢并非一定是 GC 停顿时间长导致的,队列延迟、网络延迟、其他依赖服务延迟和操作提供调度程序抖动等都可能导致响应变慢。使用 Shenandoah 时需要全面了解系统运行情况,综合分析系统响应时间。
3. JVM 常量 API
Java 12 中引入 JVM 常量 API,用来更容易地对关键类文件 (key class-fifile) 和运行时构件(artefact)的名义描述 (nominal description) 进行建模,特别是对那些从常量池加载的常量,这是一项非常技术性的变化,能够以更简单、标准的方式处理可加载常量。
具体来说就是 java.base
模块新增了 java.lang.constant
包(而非 java.lang.invoke.constant
)。包中定义了一系列基于值的符号引用(JVMS 5.1)类型,它们能够描述每种可加载常量。
引入了 ConstantDesc
接口( ClassDesc
、 MethodTypeDesc
、 MethodHandleDesc
这几个接口直接继承了 ConstantDesc
接口 )以及 Constable
接口; ConstantDesc
接口定义了 resolveConstantDesc
方法, Constable
接口定义了 describeConstable
方法; String
、 Integer
、 Long
、 Float
、 Double
均实现了这两个接口,而 EnumDesc
实现了 ConstantDesc
接口
符号引用以纯 nominal 形式描述可加载常量,与类加载或可访问性上下文区分开。有些类可以作为自己的符号引用(例如 String)。而对于可链接常量,另外定义了一系列符号引用类型,具体包括: ClassDesc
(Class 的可加载常量标称描述符) ,MethodTypeDesc
(方法类型常量标称描述符) ,MethodHandleDesc
(方法句柄常量标称描述符) 和 DynamicConstantDesc
(动态常量标称描述符) ,它们包含描述这些常量的 nominal 信息。此 API 对于操作类和方法的工具很有帮助。
3.1. String 实现了 Constable 接口
public final class String implements java.io.Serializable, Comparable<String>, CharSequence,Constable, ConstantDesc {
java.lang.constant.Constable
接口定义了抽象方法:
public interface Constable {
Optional<? extends ConstantDesc> describeConstable();
}
Java 12 String 的实现源码:
@Override public Optional<String> describeConstable() {
return Optional.of(this);
}
3.2. String#describeConstable 和 resolveConstantDesc
一个非常有趣的方法来自新引入的接口 java.lang.constant.Constable
- 它用于标记 constable 类型,这意味着这类型的值是常量,可以在 JVMS 4.4 常量池中定义。
String
的源码:
@Override
public Optional<String> describeConstable() {
return Optional.of(this);
}
@Override
public String resolveConstantDesc(MethodHandles.Lookup lookup) {
return this;
}
举例:
private static void testDescribeConstable() {
System.out.println("======test java 12 describeConstable======");
String name = "尚硅谷Java高级工程师";
Optional<String> optional = name.describeConstable();
System.out.println(optional.get());
}
4. 微基准测试套件
4.1. 何为 JMH ?
JMH,即 Java Microbenchmark Harness ,是专门用于代码微基准测试的工具套件。何谓 Micro Benchmark 呢?简单的来说就是基于方法层面的基准测试,精度可以达到微秒级。当你定位到热点方法,希望进一步优化方法性能的时候,就可以使用 JMH 对优化的结果进行量化的分析。
4.2. JMH 比较典型的应用场景
- 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;
- 对比接口不同实现在给定条件下的吞吐量;
- 查看多少百分比的请求在多长时间内完成;
4.3. JMH 的使用
要使用 JMH ,首先需要准备好 Maven 环境, JMH 的源代码以及官方提供的 Sample 就是使用 Maven 进行项目管理的
略
4.4. 新特性的说明
Java 12 中添加一套新的基本的微基准测试套件(microbenchmarks suite),此功能为 JDK 源代码添加了一套微基准测试(大约 100 个),简化了现有微基准测试的运行和新基准测试的创建过程。使开发人员可以轻松运行现有的微基准测试并创建新的基准测试,其目标在于提供一个稳定且优化过的基准。 它基于 Java Microbenchmark Harness(JMH),可以轻松测试 JDK 性能,支持 JMH 更新。
微基准套件与 JDK 源代码位于同一个目录中,并且在构建后将生成单个 jar 文件。但它是一个单独的项目,在支持构建期间不会执行,以方便开发人员和其他对构建微基准套件不感兴趣的人在构建时花费比较少的构建时间。
要构建微基准套件,用户需要运行命令:make build-microbenchmark
, 类似的命令还有:make test TEST="micro:java.lang.invoke"
将使用默认设置运行 java.lang.invoke
相关的微基准测试。
5 只保留一个 AArch64 实现
5.1. 现状
当前 Java 11 及之前版本 JDK 中存在两个 64 位 ARM 端口。这些文件的主要来源位于 src/hotspot/cpu/arm
和 open/src/hotspot/cpu/aarch64
目录中。尽管两个端口都产生了 aarch64 实现,我们将前者(由 Oracle 贡献)称为 arm64 ,将后者称为 aarch64 。
5.2. 新特性
Java 12 中将删除由 Oracle 提供的 arm64 端口相关的所有源码,即删除目录 open/src/hotspot/cpu/arm
中关于 64-bit 的这套实现,只保留其中有关 32-bit ARM 端口的实现,余下目录的 open/src/hotspot/cpu/aarch64
代码部分就成了 AArch64 的默认实现。
5.3. 目的
这将使开发贡献者将他们的精力集中在单个 64 位 ARM 实现上,并消除维护两套实现所需的重复工作。
6. 默认生成类数据共享 (CDS) 归档文件
6.1. 概述
我们知道在同一个物理机/虚拟机上启动多个 JVM 时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。所以 Java 团队引入了类数据共享机制 ( Class Data Sharing ,简称 CDS ) 的概念,通过把一些核心类在每个 JVM 间共享,每个 JVM 只需要装载自己的应用类即可。好处是:启动时间减少了,另外核心类是共享的,所以 JVM 的内存占用也减少了。
7. 可中断的 G1 Mixed GC
简言之,当 G1 垃圾回收器的回收超过暂停时间的目标,则能中止垃圾回收过程。
G1 是一个垃圾收集器,设计用于具有大量内存的多处理器机器。由于它提高了性能效率, G1 垃圾收集器最终将取代CMS 垃圾收集器。
该垃圾收集器设计的主要目标之一是满足用户设置的预期的 JVM 停顿时间。
G1 采用一个高级分析引擎来选择在收集期间要处理的工作量,此选择过程的结果是一组称为 GC 回收集( collection set ( CSet ))的区域。一旦收集器确定了 GC 回收集 并且 GC 回收、整理工作已经开始,这个过程是 without stopping 的,即 G1 收集器必须完成收集集合的所有区域中的所有活动对象之后才能停止;但是如果收集器选择过大的 GC 回收集,此时的 STW 时间会过长超出目标 pause time 。
这种情况在 mixed collections 时候比较明显。这个特性启动了一个机制,当选择了一个比较大的 collection set , Java 12 中将把 GC 回收集(混合收集集合)拆分为 mandatory (必需或强制)及 optional 两部分( 当完成 mandatory 的部 分,如果还有剩余时间则会去处理 optional 部分 )来将 mixed collections 从 without stopping 变为 abortable ,以更好满足指定 pause time 的目标。
- 其中必需处理的部分包括 G1 垃圾收集器不能递增处理的 GC 回收集的部分(如:年轻代),同时也可以包含老年代以提高处理效率。
- 将 GC 回收集拆分为必需和可选部分时,垃圾收集过程优先处理必需部分。同时,需要为可选 GC 回收集部分维护一些其他数据,这会产生轻微的 CPU 开销,但小于 1 %的变化,同时在 G1 回收器处理 GC 回收集期间,本机内存使用率也可能会增加,使用上述情况只适用于包含可选 GC 回收部分的 GC 混合回收集合。
- 在 G1 垃圾回收器完成收集需要必需回收的部分之后,如果还有时间的话,便开始收集可选的部分。但是粗粒度的处理,可选部分的处理粒度取决于剩余的时间,一次只能处理可选部分的一个子集区域。在完成可选收集部分的收集后,G1 垃圾回收器可以根据剩余时间决定是否停止收集。如果在处理完必需处理的部分后,剩余时间不足,总时间花销接近预期时间,G1 垃圾回收器也可以中止可选部分的回收以达到满足预期停顿时间的目标。
8. 增强 G1 ,自动返回未用堆内存给操作系统
8.1. 概述
上面介绍了 Java 12 中增强了 G1 垃圾收集器关于混合收集集合的处理策略,这节主要介绍在 Java 12 中同时也对 G1垃圾回收器进行了改进,使其能够在空闲时自动将 Java 堆内存返还给操作系统,这也是 Java 12 中的另外一项重大改进。
目前 Java 11 版本中包含的 G1 垃圾收集器暂时无法及时将已提交的 Java 堆内存返回给操作系统。为什么呢? G1 目前只有在 full GC 或者 concurrent cycle (并发处理周期)的时候才会归还内存,由于这两个场景都是 G1 极力避免的,因此在大多数场景下可能不会及时归还 committed Java heap memory 给操作系统。除非有外部强制执行。
在使用云平台的容器环境中,这种不利之处特别明显。即使在虚拟机不活动,但如果仍然使用其分配的内存资源,哪怕是其中的一小部分, G1 回收器也仍将保留所有已分配的 Java 堆内存。而这将导致用户需要始终为所有资源付费,哪怕是实际并未用到,而云提供商也无法充分利用其硬件。如果在此期间虚拟机能够检测到 Java 堆内存的实际使用情况,并在利用空闲时间自动将 Java 堆内存返还,则两者都将受益。
9. 其他新特性解读
上面列出的是大方面的特性,除此之外还有一些 API 的更新及废弃,主要见 JDK 12 Release Notes,这里举几个例子。
9.1. 增加项:支持 unicode 11
JDK 12 版本包括对 Unicode 11.0.0 的支持。在发布支持 Unicode 10.0.0 的 JDK 11 之后, Unicode 11.0.0 引 入了以下 JDK 12 中包含的新功能:
- 684 new characters
- 11 new blocks
- 7 new scripts
9.2. 增加项:支持压缩数字格式化
NumberFormat
添加了对以紧凑形式格式化数字的支持。紧凑数字格式是指以简短或人类可读形式表示的数字。例如,在 en_US 语言环境中, 1000
可以格式化为 1K
, 1000000
可以格式化为 1M
,具体取决于指定的样式 NumberFormat.Style
。
9.3. 增加项:String 新增方法
1. String#transform(Function)
public <R> R transform(Function<? super String, ? extends R> f) {
return f.apply(this);
}
2. String#indent
该方法允许我们调整 String 实例的缩进。
public String indent(int n) {
if (isEmpty()) {
return "";
}
Stream<String> stream = lines();
if (n > 0) {
final String spaces = " ".repeat(n);
stream = stream.map(s -> spaces + s);
} else if (n == Integer.MIN_VALUE) {
stream = stream.map(s -> s.stripLeading());
} else if (n < 0) {
stream = stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
}
return stream.collect(Collectors.joining("\n", "", "\n"));
}
9.4. 新增项: Files 新增 mismatch 方法
public static long mismatch(Path path, Path path2) throws IOException {
if (isSameFile(path, path2)) {
return -1;
}
byte[] buffer1 = new byte[BUFFER_SIZE];
byte[] buffer2 = new byte[BUFFER_SIZE];
try (InputStream in1 = Files.newInputStream(path);
InputStream in2 = Files.newInputStream(path2);) {
long totalRead = 0;
while (true) {
int nRead1 = in1.readNBytes(buffer1, 0, BUFFER_SIZE);
int nRead2 = in2.readNBytes(buffer2, 0, BUFFER_SIZE);
int i = Arrays.mismatch(buffer1, 0, nRead1, buffer2, 0, nRead2);
if (i > -1) {
return totalRead + i;
}
if (nRead1 < BUFFER_SIZE) {
// we've reached the end of the files, but found no mismatch
return -1;
}
totalRead += nRead1;
}
}
}
9.5. 新增项:其他
Collectors
新增teeing
方法用于聚合两个 downstream 的结果CompletionStage
新增exceptionallyAsync
、exceptionallyComposeAsync
方法,允许方法体在异步线程执行,同时新增了exceptionallyCompose
方法支持在exceptionally
的时候构建新的CompletionStage
- ZGC: Concurrent Class Unloading
- ZGC 在 JDK11 的时候还不支持 class unloading , JDK12 对 ZGC 支持了 Concurrent Class Unloading ,默认是
开启,使用-XX:-ClassUnloading
可以禁用
- ZGC 在 JDK11 的时候还不支持 class unloading , JDK12 对 ZGC 支持了 Concurrent Class Unloading ,默认是
- 新增
-XX:+ExtensiveErrorReports
-XX:+ExtensiveErrorReports
可以用于在 JVM crash 的时候收集更多的报告信息到 hs_err.log 文件中,product builds 中默认是关闭的,要开启的话,需要自己添加-XX:+ExtensiveErrorReports
参数
- 新增安全相关的改进
- 支持
java.security.manager
系统属性,当设置为disallow
的时候,则不使用 SecurityManager 以提升性能,如果此时调用System.setSecurityManager
则会抛出UnsupportedOperationExceptionkeytool
- 新增
-groupname
选项允许在生成 key pair 的时候指定一个 named group 新增 PKCS12 KeyStore 配置属性用于自定义 PKCS12 keystores 的生成 - Java Flight Recorder 新增了 security-related 的 event 支持 ChaCha20 and
Poly1305 TLS Cipher Suites
- 支持
9.6. 移除项
- 移除
com.sun.awt.SecurityWarnin
- 移除
FileInputStream
、FileOutputStream
、java.util.ZipFile
/Inflflator
/Deflflator
的fifinalize
方法 - 移除 GTE CyberTrust Global Root
- 移除 javac 的 -source , -target 对 6 及 1.6 的支持,同时移除 --release 选项
9.7. 废弃项
- 废弃的 API 列表见 deprecated-list
- 废弃 -XX:+/-MonitorInUseLists 选项
- 废弃 Default Keytool 的 -keyalg 值
Java 13
2019 年 9 月 17 日,国际知名的 OpenJDK 开源社区发布了 Java 编程语言环境的最新版本 OpenJDK13
1. switch 表达式(预览)
在 JDK 12 中引入了 switch 表达式作为预览特性。 JDK 13 提出了第二个 switch 表达式预览。 JEP 354 修改了这个特性,它引入了 yield 语句,用于返回值。这意味着, switch 表达式(返回值)应该使用 yield , switch 语句(不返回值)应该使用 break
在这之后, switch 中就多了一个关键字用于跳出 switch 块了,那就是 yield ,他用于返回一个值。和 return 的区别在于: return 会直接跳出当前循环或者方法,而 yield 只会跳出当前 switch 块。
2. 文本块(预览)
在 JDK 12 中引入了 Raw String Literals 特性,但在发布之前就放弃了。这个 JEP 与引入多行字符串文字( text block )在意义上是类似的。
这条新特性跟 Kotlin 里的文本块是类似的。
现实问题
在 Java 中,通常需要使用 String 类型表达 HTML , XML , SQL 或 JSON 等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。
文本块就是指多行字符串,例如一段格式化后的 XML 、 JSON 等。而有了文本块以后,用户不需要转义, Java 能自动搞定。因此,文本块将提高 Java 程序的可读性和可写性。
目标
- 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写 Java 程序
- 增强 Java 程序中字符串的可读性
"""
<html>
<body>
<p>Hello, 尚硅谷</p>
</body>
</html>
"""
具体使用
- 文本块是 Java 语言中的一种新文字。它可以用来表示任何字符串,并且提供更大的表现力和更少的复杂性。
- 文本块由零个或多个字符组成,由开始和结束分隔符括起来。
- 开始分隔符是由三个双引号字符("""),后面可以跟零个或多个空格,最终以行终止符结束。文本块内容以开始分隔符的行终止符后的第一个字符开始。
- 结束分隔符也是由三个双引号字符(""")表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。
- 文本块中的内容可以直接使用",",但不是必需的。
- 文本块中的内容可以直接包括行终止符。允许在文本块中使用 \n ,但不是必需的。
3. 动态 CDS 档案(动态类数据共享归档)
CDS ,是 Java 12 的特性了,可以让不同 Java 进程之间共享一份类元数据,减少内存占用,它还能加快应用的启动速度。而 JDK13 的这个特性支持在 Java application 执行之后进行动态 archive 。存档类将包括默认的基础层 CDS 存档中不存在的所有已加载的应用程序和库类。也就是说,在 Java 13 中再使用 AppCDS 的时候,就不再需要这么复杂了。
该提案处于目标阶段,旨在提高 AppCDS 的可用性,并消除用户进行试运行以创建每个应用程序的类列表的需要。
4. ZGC:取消使用未使用的内存
4.1. G1 和 Shenandoah
JVM 的 GC 释放的内存会还给操作系统吗?
GC 后的内存如何处置,其实是取决于不同的垃圾回收器。因为把内存还给 OS ,意味着要调整 JVM 的堆大小,这个过程是比较耗费资源的。
HotSpot 的 G1 和 Shenandoah 这两个 GC 已经提供了这种能力,并且对某些用户来说,非常有用。因此, Java13 则给 ZGC 新增归还 unused heap memory 给操作系统的特性。
5. 重新实现旧版套接字 API
5.1. 现有问题
重新实现了古老的 Socket 接口。现在已有的 java.net.Socket
和 java.net.ServerSocket
以及它们的实现类,都可以回溯到 JDK 1.0 时代了。
- 它们的实现是混合了 Java 和 C 的代码的,维护和调试都很痛苦。
- 实现类还使用了线程栈作为 I/O 的缓冲,导致在某些情况下还需要增加线程栈的大小。
- 支持异步关闭,此操作是通过使用一个本地的数据结构来实现的,这种方式这些年也带来了潜在的不稳定性和跨平台移植问题。该实现还存在几个并发问题,需要彻底解决。
在未来的网络世界,要快速响应,不能阻塞本地方法线程,当前的实现不适合使用了。
5.2. 新的实现类
全新实现的 NioSocketImpl
来替换 JDK1.0 的 PlainSocketImpl
- 它便于维护和调试,与 NewI/O (NIO) 使用相同的 JDK 内部结构,因此不需要使用系统本地代码。
- 它与现有的缓冲区缓存机制集成在一起,这样就不需要为 I/O 使用线程栈。
- 它使用
java.util.concurrent
锁,而不是synchronized
同步方法,增强了并发能力。 - 新的实现是Java 13中的默认实现,但是旧的实现还没有删除,可以通过设置系统属性
jdk.net.usePlainSocketImpl
来切换到旧版本。
6. 其他解读
上面列出的是大方面的特性,除此之外还有一些 API 的更新及废弃,主要见 JDK 13 Release Notes ,这里举几个例子。
6.1. 增加项
- 添加
FileSystems.newFileSystem(Path, Map<String, ?>)
方法 - 新的 java.nio.ByteBuffer Bulk get/put 方法 Transfer Bytes Without Regard to Buffer Position
- 支持 Unicode 12.1
- 添加
-XX:SoftMaxHeapSize
Flag,目前仅仅对 ZGC 起作用 - ZGC 的最大 heap 大小增大到 16 TB
6.2. 移除项
- 移除 awt.toolkit System Property
- 移除 Runtime Trace Methods
- 移除
-XX:+AggressiveOpts
- 移除 Two Comodo Root CA Certifificates 、Two DocuSign Root CA Certifificates
- 移除内部的
com.sun.net.ssl
包
6.3. 废弃项
- 废弃
-Xverify:none
及-noverify
- 废弃 rmic Tool 并准备移除
- 废弃
javax.security.cert
并准备移除
6.4. 已知问题
- 不再支持 Windows 2019 Core Server
- 使用 ZIP File System ( zipfs ) Provider 来更新包含 Uncompressed Entries 的 ZIP 或 JAR 可能造成文件损坏
6.5. 其他事项
GraphicsEnvironment.getCenterPoint()
及getMaximumWindowBounds()
已跨平台统一- 增强了 JAR Manifest 的 Class-Path 属性处理
- 针对 Negatively Sized Argument , StringBuffer ( CharSequence )及 StringBuilder ( CharSequence )会抛出 NegativeArraySizeException
- Linux 的默认进程启动机制已经使用 posix_spawn
Lookup.unreflflectSetter(Field)
针对static final fields
会抛出IllegalAccessException
- 使用了
java.net.Socket.setSocketImplFactory
及java.net.ServerSocket.setSocketFactory
方法的要注意,要求客户端及服务端要一致,不能一端使用自定义的 factory ,一端使用默认的 factory SocketImpl
的supportedOptions
,getOption
及setOption
方法的默认实现发生了变化,默认的supportedOptions
返回空,而默认的getOption
,及setOption
方法抛出UnsupportedOperationException
- JNI NewDirectByteBuffer 创建的 Direct Buffer 为
java.nio.ByteOrder.BIG_ENDIAN
Base64.Encoder
及Base64.Decoder
可能抛出OutOfMemoryError
- 改进了 Serial GC Young pause time report
- 改进了
MaxRAM
及UseCompressedOops
参数的行为
7. 小结
以上,就是 JDK 13 中包含的主要特性。
- 语法层面,改进了 Switch Expressions ,新增了 Text Blocks ,二者皆处于 Preview 状态;
- API 层面主要使用
NioSocketImpl
来替换 JDK1.0 的PlainSocketImpl
- GC 层面则改进了ZGC,以支持 Uncommit Unused Memory
而且, JDK13 并不是 LTS (长期支持)版本,如果你正在使用 Java 8 ( LTS )或者 Java 11 ( LTS ),暂时可以不必升级到 Java 13 。
这些年很多 Java 粉丝已经讨厌写那些冗长难看的代码了,有些转投到高效的 Python 门下,有的转用 Kotlin ,有的去了新兴的 Go 那边。不过, Java 大叔凭着其广阔的领土,活跃高效的运转机构,以及其开放改革的心,还会有不少的粉丝追随他的。
Java 14
概述
2020年 3 月 17 日,JDK/Java 14 正式 GA (General Available) 。这是自从 Java 采用六个月一次的发布周期之后的第五次发布。
Java14 发布,16 大新特性,代码更加简洁明快
此版本包含的 JEP(Java/JDK Enhancement Proposals,JDK 增强提案)比 Java 12 和 13 加起来的还要多。总共 16 个新特性,包括两个孵化器模块、三个预览特性、两个弃用的功能以及两个删除的功能。
- 孵化器模块:将尚未定稿的 API 和工具先交给开发者使用,以获得反馈,并用这些反馈进一步改进Java平台的质量。
- 预览特性:是规格已经成型、实现已经确定,但还未最终定稿的功能。它们出现在 Java 中的目的是收集在真实世界中使用后的反馈信息,促进这些功能的最终定稿。这些特性可能会随时改变,根据反馈结果,这些特性甚至可能会被移除,但通常所有预览特性最后都会在 Java 中固定下来。
Mark Reinhold :他是 JDK 1.2 和 5.0 版本的首席工程师, Java SE 6 的规范制定的负责人,还是 JDK 7 , JDK 8 和 JDK 9 项目和规范的负责人,目前,他主要在 OpenJDK 社区领导 JDK 项目
Oracle JDK 不再免费提供。但是,你现在可以从包括 Oracle 在内的各种供应商获得免费的 OpenJDK 发行版。
1. JEP 305:instanceof 的模式匹配(预览)
这个特性很有意思,因为它为更为通用的模式匹配打开了大门。模式匹配通过更为简便的语法基于一定的条件来抽取对象的组件,而 instanceof
刚好是这种情况,它先检查对象类型,然后再调用对象的方法或访问对象的字段。
有了该功能,可以减少 Java 程序中显式强制转换的数量,从而提高生产力,还能实现更精确、简洁的类型安全的代码。
public class Feature01 {
@Test
public void test1() {
Object obj = new String("hello,Java14");
obj = null;//在使用null 匹配instanceof 时,返回都是false.
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.contains("Java"));
} else {
System.out.println("非String类型");
}
//举例1:
if (obj instanceof String str) { //新特性:省去了强制类型转换的过程
System.out.println(str.contains("Java"));
} else {
System.out.println("非String类型");
}
}
}
// 举例2
class InstanceOf {
String str = "abc";
public void test(Object obj) {
if (obj instanceof String str) {//此时的str的作用域仅限于if结构内。
System.out.println(str.toUpperCase());
} else {
System.out.println(str.toLowerCase());
}
}
}
//举例3:
class Monitor {
private String model;
private double price;
// public boolean equals(Object o){
// if(o instanceof Monitor other){
// if(model.equals(other.model) && price == other.price){
// return true;
// }
// }
// return false;
// }
public boolean equals(Object o) {
return o instanceof Monitor other && model.equals(other.model) && price == other.price;
}
}
2. JEP 358:非常实用的 NullPointerException
该特性改进了 NullPointerException
的可读性,能更准确地给出 null 变量的信息。 该特性可以帮助开发者提高生产力,以及改进各种开发工具和调试工具的质量。一个目标是减少开发人员的困惑和担忧 。
- 该特性可以更好地提示哪个地方出现的空指针,需要通过
-XX:+ShowCodeDetailsInExceptionMessages
开启 - 在未来的版本中,这个特性可能会默认启用
- 这个增强特性不仅适用于方法调用,只要会导致
NullPointerException
的地方也都适用,包括字段的访问、数组的访问和赋值。
3. JEP 359:Record(预览特性)
早在2019年2月份,Java 语言架构师 Brian Goetz,曾经写过一篇文章,详尽的说明了并吐槽了Java语言,他和很多程序员一样抱怨“Java太啰嗦”或有太多的“繁文缛节”,他提到:开发人员想要创建纯数据载体类(plain data carriers)通常都必须编写大量低价值、重复的、容易出错的代码。如:构造函数、getter/setter、equals() 、hashCode() 以及 toString() 等。
以至于很多人选择使用IDE的功能来自动生成这些代码。还有一些开发会选择使用一些第三方类库,如 Lombok 等来生成这些方法,从而会导致了令人吃惊的表现(surprising behavior)和糟糕的可调试性(poor debuggability)
为了避免这种重复代码,Java 14 推出 record类型
使用 record 来减少类声明语法,效果类似 Lombok 的 @Data
注解, Kotlin 中的 data class 。它们的共同点是类的部分或全部状态可以直接在类头中描述,并且这个类中只包含了纯数据而已。
- 当你用 record 声明一个类时,该类将自动拥有以下功能:
- 获取成员变量的简单方法,以下面代码为例
name()
和partner()
。注意区别于我们平常 getter的写法。 - 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性
- 重写 equals 当然要重写 hashCode
- 一个可以打印该类所有成员属性的 toString 方法。
- 请注意只会有一个构造方法。
- 获取成员变量的简单方法,以下面代码为例
- 和枚举类型一样,记录也是类的一种受限形式。作为回报,记录对象在简洁性方面提供了显著的好处。
- 还可以在 Record 声明的类中定义静态字段、静态方法、构造器或实例方法。
- 不能在 Record 声明的类中定义实例字段;类不能声明为
abstract
;不能声明显式的父类等。 - 为了在 Java 14 中引入这种新类型,需要在
java.lang.Class
对象中添加如下两个新方法:RecordComponent[] getRecordComponents()
boolean isRecord()
示例
public record Person(String name, Person partner) {
//还可以声明静态的属性、静态的方法、构造器、实例方法
public static String nation;
public static String showNation() {
return nation;
}
public Person(String name) {
this(name, null);
}
public String getNameInUpperCase() {
return name.toUpperCase();
}
//不可以声明非静态的属性
// private int id;//报错
}
//不可以将record定义的类声明为abstract的
//abstract record Order(){
//
//}
//不可以给record定义的类声明显式的父类(非Record类)
//record Order() extends Thread{
//
//}
public class Feature03 {
@Test
public void test1(){
//测试构造器
Person p1 = new Person("罗密欧",new Person("zhuliye",null));
//测试toString()
System.out.println(p1);
//测试equals():
Person p2 = new Person("罗密欧",new Person("zhuliye",null));
System.out.println(p1.equals(p2));
//测试hashCode()和equals()
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
for (Person person : set) {
System.out.println(person);
}
//测试name()和partner():类似于getName()和getPartner()
System.out.println(p1.name());
System.out.println(p1.partner());
}
@Test
public void test2(){
Person p1 = new Person("zhuyingtai");
System.out.println(p1.getNameInUpperCase());
Person.nation = "CHN";
System.out.println(Person.showNation());
}
}
4. JEP 361:switch表达式
- 这是 JDK 12 和 JDK 13 中的预览特性,现在是正式特性了。
- 该特性规定, switch 可以当作语句使用,也可以当作表达式使用。
- 这可以简化日常的编码方式,也为本版本中预览的模式匹配( JEP 305 )特性打下了基础
- 具体情况:使用
->
来替代以前的:break
;另外还提供了yield
来在 block 中返回值
5. JEP 368 :文本块(预览第二版)
- JDK 13 引入的 text blocks 进行第二轮 preview , JDK 14 的版本主要增加了两个 escape
sequences ,分别是 \与 \s escape sequence - 现实问题
- 在 Java 中,通常需要使用 String 类型表达 HTML , XML , SQL 或 JSON 等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。
- 目标
- 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写 Java 程序。
- 增强 Java 程序中用字符串表示的其他语言的代码的可读性
- 解析新的转义序列
6. JEP 366:弃用 Parallel Scavenge 和 SerialOld GC 组合
-
由于维护和兼容性测试的成本,在 JDK 8 时将 Serial+CMS 、 ParNew+Serial Old 这两个组合声明为废弃( JEP 173 ),并在 JDK 9 中完全取消了这些组合的支持( JEP214 )
-
ParallelScavenge + SerialOld GC 的 GC 组合要被标记为 Deprecated 了
-
JDK 官方给出将这个 GC 组合标记为 Deprecate 的理由是:这个 GC 组合需要大量的代码维护工作,并且,这个 GC 组合很少被使用。因为它的使用场景应该是一个很大的 Young 区配合一个很小的 Old 区,这样的话, Old 区用 Serial Old GC 去收集时停顿时间我们才能勉强接受。
-
废弃了 parallel young generation GC 与 Serial Old GC 的组合(
-XX:+UseParallelGC
与
-XX:-UseParallelOldGC
配合开启),现在使用-XX:+UseParallelGC
、-XX:-UseParallelOldGC
或者-XX:-UseParallelOldGC
都会出现告警如下:Java HotSpot(TM) 64-Bit Server VM warning: Option UseParallelOldGC was deprecated in version 14.0 and will likely be removed in a future release.
7. JEP 363:删除 CMS 垃圾回收器
- 该来的总会来,自从 G1 (基于 Region 分代)横空出世后, CMS 在 JDK9 中就被标记为 Deprecate 了( JEP 291 : Deprecate the Concurrent Mark Sweep ( CMS ) Garbage Collector )
- CMS 的弊端 :
- 会产生内存碎片,导致并发清除后,用户线程可用的空间不足
- 既然强调了并发( Concurrent ), CMS 收集器对 CPU 资源非常敏感
- CMS 收集器无法处理浮动垃圾
- 上述的这些问题,尤其是碎片化问题,给你的 JVM 实例就像埋了一颗炸弹。说不定哪次就在你的业务高峰期来一次 Full GC 。当 CMS 停止工作时,会把 Serial Old GC 作为备选方案,而 Serial Old GC 是 JVM 中性能最差的垃圾回收方式,停顿个几秒钟,上十秒都有可能。
- 移除了 CMS 垃圾收集器,如果在 JDK 14 中使用
-XX: +UseConcMarkSweepGC
的话,
JVM 不会报错,只是给出一个 warning 信息。 - 现在 G1 回收器已成为默认回收器好几年了。
- 我们还看到了引入了两个新的收集器:ZGC ( JDK 11 出现)和 Shenandoah
( open jdk 12 )。- 主打特点:低停顿时间
8-9. JEP:ZGC on macOS 和 Windows
令人震惊、革命性的 ZGC
-
ZGC 与 Shenandoah 目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。
-
《深入理解 Java 虚拟机》一书中这样定义 ZGC : ZGC 收集器是一款基于 Region 内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记 - 压缩算法的,以低延迟为首要目标的一款垃圾收集器。
-
虽然 ZGC 还在试验状态,没有完成所有特性,但此时性能已经相当亮眼,用“令人震惊、革命性”来
形容,不为过。未来将在服务端、大内存、低延迟应用的首选垃圾收集器。 -
JEP 364:ZGC 应用在 macOS 上
-
JEP 365:ZGC 应用在 Windows 上
-
JDK14 之前, ZGC 仅 Linux 才支持
-
尽管许多使用 ZGC 的用户都使用类 Linux 的环境,但在 Windows 和 macOS 上,人们也需要 ZGC 进行开发部署和测试。许多桌面应用也可以从 ZGC 中受益。因此, ZGC 特性被移植到了 Windows 和 macOS 上
-
现在 mac 或 Windows 上也能使用 ZGC 了,示例如下:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
10. JEP 343:打包工具(孵化器模块)
- 这个孵化器工具为开发者带来了一种打包 Java 应用的方式,目的在于创建一个简单的打包工具,可以用于构建 exe 、 pkg 、 dmg 、 deb 、 rpm 格式的安装文件
- JDK 14 引入了
jdk.incubator.jpackage.jmod
,它基于 JavaFX javapackager tool 构建。
11. JEP 345 : G1 的 NUMA-Aware 的内存分配
该功能改进了 G1 垃圾回收器在非一致内存访问( NUMA )系统上的整体性能。
NUMA 就是非统一内存访问架构(英语: non-uniform memory access ,简称 NUMA ),是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。
12. JEE 349:JFR 事件流
Java 为了更方便的了解运行的 JVM 情况,在之前的 JDK 11 版本中引入了 JFR 特性,即 JDK Flight Recorder 。但是使用不太灵活。虽然 JVM 通过 JFR 暴露了超过 500 项数据,但是其中大部分数据只能通过解析 JFR 日志文件才能获取得到,而不是实时获取。用户想要使用 JFR 的数据的话,用户必须先开启 JFR 进行记录,然后停止记录,再将飞行记录的数据 Dump 到磁盘上,然后分析这个记录文件。
举例:
jcmd <PID> JFR.start name=test duration=60s settings=template.jfc
filename=output.jfr
新特性中,可以公开 JDK Flight Recorder ( JFR )的数据,用于持续监视,从而简化各种工具和应用程序对 JFR 数据的访问。
13. JEP 352:非易失性映射字节缓冲区
在 JEP 352 中,对 FileChannel
API 进行了扩展,以允许创建 MappedByteBuffer
实例。
与易失性存储器( RAM )不同,它们在非易失性数据存储( NVM ,非易失性存储器)上工作。但是,目标平台是 Linux x64 。 非易失性内存能够持久保持数据,因此可以利用该特性来改进性能。
其他新特性
- 14.JEP 370:外部内存访问 API
- 15.JEP 362:弃用 Solaris 和 SPARC 的移植
- 16.JEP 367:删除 Pack200 工具和 API
上面列出的是大方面的特性,除此之外还有一些 API 的更新及废弃,主要见 JDK 14 Release Notes
Java 15
概览
JDK 15 在 2020 年 9 月 15 号正式发布
Java 15 为用户提供了 14 项主要的增强/更改提案,主要包括孵化器模块,预览功能,确定之前版本预览的功能,不推荐使用的功能以及两个删除功能。
Oracle 采取 小步快跑,快速迭代 ,孵化器模块(Incubator)和预览特性(Preview)。
- 孵化器模块(Incubator/孵化版/实验版)
- 尚未定稿的 API / 工具,主要用于从 Java 社区收集使用反馈,稳定性无保障,后期有较大可能性移除
- 预览特性(Preview/预览版)
- 规格已成型,实现已确定,但还未最终定稿。这些特性还是存在被移除的可能性,但一般来说最后都会被固定下来。
新特性预览
对应中文特性:(JEP:JDK Enhancement Proposals,JDK 增强建议,也就是 JDK 的特性新增和改进提案。)
- JEP 339:EdDSA 数字签名算法
- JEP 360:密封类(预览)
- JEP 371:隐藏类
- JEP 372:移除 Nashorn JavaScript 引擎
- JEP 373:重新实现 Legacy DatagramSocket API
- JEP 374:禁用偏向锁定
- JEP 375:instanceof 模式匹配(第二次预览)
- JEP 377:ZGC:一个可扩展的低延迟垃圾收集器
- JEP 378:文本块
- JEP 379:Shenandoah:低暂停时间垃圾收集器
- JEP 381:移除 Solaris 和 SPARC 端口
- JEP 383:外部存储器访问 API(第二次孵化版)
- JEP 384:Records(第二次预览)
- JEP 385:废弃 RMI 激活机制
JDK15 整体来看新特性方面并不算很亮眼,它主要是对之前版本预览特性的功能做了确定,如文本块、ZGC 等,这么一来我们就可以放心大胆的使用了。
1. JEP 375:instanceof 自动匹配模式(第二次预览)
public void patternMatching(Object obj) {
if (obj instanceof String str) {
// can use str here
System.out.println(str.length());
} else {
// can't use str here
}
}
由于 instanceof
的模式匹配是预览功能,需要通过选项 --enable-preview --source 14
来启用。下面的代码中用到了 JDK 11 引入的使用 java
运行 Java 源代码的功能。
需要带上 --enable-preview
允许预览机制进行编译。
javac --enable-preview -source 14 InstanceofDemo.java
需要带上 --enable-preview
允许预览机制进行执行。
java --enable-preview InstanceofDemo
2. JEP 378:文本块功能(转正)
文本块是一种多行字符串文字,它避免了大多数转义序列的需要,以一种可预测的方式自动设置字符串的格式,并在需要时使开发人员可以控制格式,简化编写 Java 程序的任务。
String html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
System.out.println("""
Hello,
itheima
text blocks!
""");
3. JEP 384:Records(二次预览)
使用 Record 可以更方便的创建一个常量类,通过 record 增强 Java 编程语言。 record 提供了一种紧凑的语法来声明类,这些类是浅层不可变数据的透明持有者。
从表面上看,将 Record 是为了简化模板编码而生的,但是它还有“远大”的目标: modeling data as data (将数据建模为数据)。 record 应该更简单、简洁、数据不可变。
record 是 Java 的一种新的类型。同枚举一样, record 也是对类的一种限制。 record 放弃了类通常享有的特性:将 API 和表示解耦。但是作为回报, record 使数据类变得非常简洁。
一个 record 具有名称和状态描述。状态描述声明了 record 的组成部分。例如:
record People(String name, int age) { }
Compiled from "RecordDemo01.java"
final class People extends java.lang.Record {
public People(java.lang.String, int);
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public java.lang.String name();
public int age();
}
因为 record 在语义上是数据的简单透明持有者,所以记录会自动获取很多标准成员:
- 状态声明中的每个成员,都有一个 private final 的字段;
- 状态声明中的每个组件的公共读取访问方法,该方法和组件具有相同的名字;
- 一个公共的构造函数,其签名与状态声明相同;
- equals 和 hashCode 的实现;
- toString 的实现。
也可以显式声明从状态描述自动派生的任何成员。可以在没有正式参数列表的情况下声明构造函数(这种情况下,假定与状态描述相同),并且在正常构造函数主体正常完成时调用隐式初始化(this.x=x)。这样就可以在显式构造函数中仅执行其参数的验证等逻辑,并省略字段的初始化,例如:
record Test(int x, int y) {
public Test {
if (x > y)
System.out.println("第一个参数大于第二个参数~");
}
}
限制
- records 不能扩展任何类,并且不能声明私有字段以外的实例字段。声明的任何其他字段都必须是静态的。
- records 类都是隐含的 final 类,并且不能是抽象类。这些限制使得 records 的 API 仅由其状态描述定义,并且以后不能被其他类实现或继承。
4. JEP 360:密封的类和接口(预览)
通过密封的类和接口来增强 Java 编程语言,这是新的预览特性。Sealed 密封用于限制父类的使用,密封的类和接口可以限制被谁继承和实现。
这个特性的目标包括——允许类或接口的开发者来控制哪些代码负责实现,提供了比 final
修饰超类的声明的更多选择。
在Java中,类层次结构通过继承实现代码的重用,父类的方法可以被许多子类继承。但是,类层次结构的目的并不总是重用代码。有时,其目的是对域中存在的各种可能性进行建模,例如图形库支持的形状类型或金融应用程序支持的贷款类型。当以这种方式使用类层次结构时,我们可能需要限制子类集从而来简化建模。
具体使用:因为我们引入了 sealed class 或 interfaces ,这些 class 或者 interfaces 只允许被指定的类或者 interface 进行继承和实现。使用修饰符 sealed ,您可以将一个类声明为密封类。密封的类使用关键字 permits
列出可以直接扩展它的类。子类可以是最终的,非密封的或密封的。
package com.itheima.demo04_sealed;
/**
* 目标: JEP 360:Sealed Classes(Preview)密封的类和接口(预览)
*/
public sealed class Person permits Teacher, Student, SportMan, Doctor {
} //人类
final class Teacher extends Person {
} //老师
sealed class Student extends Person permits MiddleSchoolStudent, UniversityStudent {
} //学生
final class MiddleSchoolStudent extends Student {
} //中学生
final class UniversityStudent extends Student {
} //大学生
non-sealed class SportMan extends Person {
} //运动员
non-sealed class Doctor extends Person {
} // 医生
class PingPongMan extends SportMan {
}
5. JEP 371:Hidden Classes (隐藏类)
该提案通过启用标准 API 来定义 无法发现 且具有 有限生命周期 的隐藏类,从而提高 JVM 上所有语言的效率。隐藏类是为框架(frameworks)所设计的,隐藏类不能直接被其他类的字节码使用,只能在运行时生成类并通过反射间接使用它们。通常来说基于 JVM 的很多语言都有动态生成类的机制,这样可以提高语言的灵活性和效率。
- 隐藏类天生为框架设计的,在运行时生成内部的 class
- 隐藏类只能通过反射访问,不能直接被其他类的字节码访问。
- 隐藏类可以独立于其他类加载、卸载,这可以减少框架的内存占用。
Hidden Classes 就是不能直接被其他 class 的二进制代码使用的 class 。 Hidden Classes 主要被一些框架用来生成运行时类,但是这些类不是被用来直接使用的,而是通过反射机制来调用。
比如在 JDK8 中引入的 lambda 表达式, JVM 并不会在编译的时候将 lambda 表达式转换成为专门的类,而是在运行时将相应的字节码动态生成相应的类对象。
普通类是通过调用 ClassLoader::defineClass
创建的,而隐藏类是通过调用 Lookup::defineHiddenClass
创建的。这使 JVM 从提供的字节中派生一个隐藏类,链接该隐藏类,并返回提供对隐藏类的反射访问的查找对象。调用程序可以通过返回的查找对象来获取隐藏类的 Class 对象。
6. JEP 377:ZGC可扩展的低延迟垃圾收集器(转正)
ZGC 是一个重新设计的并发的垃圾回收器,通过减少 GC 停顿时间来提高性能。
但是这并不是替换默认的 GC ,默认的 GC 仍然还是 G1 。之前需要通过 -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
来启用 ZGC ,现在只需要 -XX:+UseZGC
就可以。相信不久的将来它必将成为默认的垃圾回收器。
7. JEP 339:EdDSA 数字签名算法
新加入基于 Edwards-Curve 数字签名算法( EdDSA-Edwards-Curve Digital Signature Algorithm )的加密签名,即爱德华兹曲线数字签名算法。
与 JDK 中的现有签名方案相比, EdDSA 具有更高的安全性和性能,因此备受关注。它已经在 OpenSSL 和 BoringSSL 等加密库中得到支持,在区块链领域用的比较多。
EdDSA 是一种现代的椭圆曲线方案,具有 JDK 中现有签名方案的优点。 EdDSA 将只在 SunEC 提供商中实现。
8. JEP 373:重新实现 DatagramSocket API
新的计划是 JEP 353 的后续,该方案重新实现了遗留的套接字 API
java.net.datagram.Socket
和 java.net.MulticastSocket
的当前实现可以追溯到 JDK 1.0 ,那时 IPv6 还在开发中。因此,当前的多播套接字实现尝试调和 IPv4 和 IPv6 难以维护的方式。
通过替换 java.net.datagram
的基础实现,重新实现旧版 DatagramSocket
API
更改 java.net.DatagramSocket
和 java.net.MulticastSocket
为更加简单、现代化的底层实现。提高了 JDK 的可维护性和稳定性。
通过将 java.net.datagram.Socket
和 java.net.MulticastSocket
API 的底层实现替换为更简单、更现代的实现来重新实现遗留的 DatagramSocket
API
新的实现:
- 易于调试和维护
- 与 Project Loom 中正在探索的虚拟线程协同
9. JEP 374:禁用偏向锁定
在默认情况下禁用偏向锁定,并弃用所有相关命令行选项。目标是确定是否需要继续支持偏置锁定的高维护成本的遗留同步优化,HotSpot 虚拟机使用该优化来减少非竞争锁定的开销。尽管某些Java应用程序在禁用偏向锁后可能会出现性能下降,但偏向锁的性能提高通常不像以前那么明显。
该特性默认禁用了 biased locking( -XX:+UseBiasedLocking
),并且废弃了所有相关的命令行选型
10. JEP 379:Shenandoah 垃圾回收算法转正
Shenandoah 垃圾回收算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。
怎么形容Shenandoah和ZGC的关系呢?异同点大概如下:
- 相同点:性能几乎可认为是相同的
- 不同点: ZGC 是 Oracle JDK 的,根正苗红。而 Shenandoah 只存在于 OpenJDK 中,因此使用时需注意你的 JDK 版本
打开方式:使用 -XX:+UseShenandoahGC
命令行参数打开。
11. JEP 383:外部存储器访问 API(二次孵化)
目的是引入一个 API,以允许 Java 程序安全、有效地访问 Java 堆之外的外部存储器。如本机、持久和托管堆。
有许多 Java 程序是访问外部内存的,比如 Ignite 和 MapDB 。该 API 将有助于避免与垃圾收集相关的成本以及与跨进程共享内存以及通过将文件映射到内存来序列化和反序列化内存内容相关的不可预测性。该 Java API 目前没有为访问外部内存提供令人满意的解决方案。但是在新的提议中, API 不应该破坏 JVM 的安全性。
Foreign-Memory Access API 在 JDK14 被作为 incubating API 引入,在 JDK15 处于 Second Incubator ,提供了改进。
12. JEP 381:移除 Solaris 和 SPARC 端口
近年来,Solaris 和 SPARC 都已被 Linux 操作系统和英特尔处理器取代。放弃对 Solaris 和 SPARC 端口的支持将使 OpenJDK 社区的贡献者能够加速开发新功能,从而推动平台向前发展。
13. JEP 372:移除了 Nashorn
JavaScript 脚本引擎
Nashorn 是在 JDK 提出的脚本执行引擎,该功能是 2014 年 3 月发布的 JDK 8 的新特性。在 JDK11 就已经把它标记为废弃了, JDK15 完全移除。
在 JDK11 中取以代之的是 GraalVM 。 GraalVM 是一个运行时平台,它支持 Java 和其他基于 Java 字节码的语言,但也支持其他语言,如 JavaScript , Ruby , Python 或 LLVM 。性能是 Nashorn 的 2 倍以上。
JDK15 移除了 Nashorn JavaScript Engine 及 jjs 命令行工具。具体就是 jdk.scripting.nashorn 及 jdk.scripting.nashorn.shell 这两个模块被移除了。
补充:Graal VM 在 HotSpot VM 基础上增强而成的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用。语言包括: Java 、 Scala 、 Groovy 、 Kotlin 、 C 、 C++ 、 JavaScript 、 Ruby 、 Python 、 R 等
14. JEP 385:废除 RMI 激活以便在将来进行删除
RMI Activation 被标记为 Deprecated ,将会在未来的版本中删除。 RMI 激活机制是 RMI 中一个过时的部分,自 Java 8 以来一直是可选的而非必选项。 RMI 激活机制增加了持续的维护负担。 RMI 的其他部分暂时不会被弃用。
在 RMI 系统中,我们使用延迟激活。延迟激活将激活对象推迟到客户第一次使用(即第一次方法调用)之前。
既然 RMI Activation 这么好用,为什么要废弃呢?
因为对于现代应用程序来说,分布式系统大部分都是基于 Web 的, web 服务器已经解决了穿越防火墙,过滤请求,身份验证和安全性的问题,并且也提供了很多延迟加载的技术。
所以在现代应用程序中, RMI Activation 已经很少被使用到了。并且在各种开源的代码库中,也基本上找不到 RMI Activation 的使用代码了。
为了减少 RMI Activation 的维护成本,在 JDK8 中, RMI Activation 被置为可选的。现在在 JDK15 中,终于可以废弃了。