背景
2022 年 Spring 6 和 SpringBoot 3 相继推出。在此之前,Java 社区一直是"新版任你发,我用 Java 8",不管新版本怎么出,很少有人愿意升级。
这一次,Spring 直接来了个大招,Spring 6 和 SpringBoot 3 的最低依赖就是JDK17!跨过 JDK 8 ~ JDK 16,直接升级到 JDK 17。
那么,为什么是 JDK 17 呢?
为什么是 JDK 17
有这么多新版本的 JDK,而且 2022 年还会推出 JDK 18 和 JDK 19,为什么 Spring 选择了 JDK 17 呢?
主要因为它是一个 Oracle 官宣可以免费商用的 LTS 版本。LTS 即 Long Term Support,也就是官方保证会长期支持的版本。
JDK8
新语法
- Lambda 表达式和函数式接口
- 接口默认方法和静态方法
核心类库
- Optional Java 8也将Optional加入了官方库。 解决NullPointerException。
- Stream流,新增的Stream API(java.util.stream将生成环境的函数式编程引入了Java库中。
- Date/Time API
Clock 类LocalDate、LocalTime和LocalDateTime
Clock.systemUTC() 等价于System.currentTimeMillis() 和 TimeZone.getDefault()
ZoneDateTime
Duration类
- Base64 引入官方库 java.util.Base64
JVM
- 使用 Metaspace (JEP 122)代替持久代(PermGen space)
疑问:为何要在jdk8中消除永久代嘞 作者动机是什么?
JEP 122 消除永久代 openjdk.org/jeps/122
动机:这是 JRockit 和 Hotspot 融合工作的一部分。
JRockit 客户不需要配置永久代(因为 JRockit 没有永久代) ,并且习惯于不配置永久代。
JRockit说明:Oracle JRockit (原来的 Bea JRockit)系列产品是一个全面的Java运行时解决方案组合,包括了行业最快的标准Java解决方案。
该调整面临风险:将内部字符串和类静态值移动到 Java 堆可能会导致内存不足异常或 GC 数量的增加。可能需要用户对 -Xmx 进行一些调整。
JDK9
GC
- 默认GC是使用G1
- 弃用的垃圾收集器 (GC) 组合
ParNew + SerialOld
DefNew + CMS - Deprecates 弃用并发标记扫描 (CMS) 垃圾收集器 JEP 291 openjdk.org/jeps/291
为何要弃用CMS?
(G1)垃圾收集器完全替代 CMS 的大多数用法。
废弃并发标记清除(CMS)垃圾收集器,以便在将来的主要版本中停止对它的支持。
研发者动机和目标:加速 HotSpot 中其他垃圾收集器的开发。减少 GC 代码库的维护负担,并加速新的开发。
核心类库
- HTTP/2客户端
Java 9 引入了新的 API,它使用起来更干净、更清晰,并且还增加了对 HTTP/2 的支持。
- Collection(集合) API更新
//在 Java 9 中为集合的创建增加了静态工厂创建方式,也就是 of 方法,通过静态工厂 of 方法创建的集合是只读集合,里面的对象不可改变。
List namesList = List.of("Lokesh", "Amit", "John");
Set namesSet = Set.of("Lokesh", "Amit", "John");
Map namesMap = Map.ofEntries(Map.entry("1", "Lokesh"),Map.entry("2", "Amit"),Map.entry("3", "Brian"));
- Stream(流) API改进
Java 9 引入了两种与 Streams 交互的新方法,即takeWhile/dropWhile方法。
List alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
List one = alphabets.stream()
//takeWhile: 从头开始筛选,遇到不满足的就结束了。
.takeWhile(s -> !s.equals("d"))
.collect(Collectors.toList());
//打印出:[a, b, c]
System.out.println(one);
List alphabets2 = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
List subset2 = alphabets2.stream()
.dropWhile(s -> !s.equals("d")) //odropWhile: 从头开始删除,遇到不满足的就结束了。
.collect(Collectors.toList());
// 打印出:[d, e, f, g, h, i]
System.out.println(subset2);
/*** 此外,它还添加了两个重载方法,即ofNullable和iterate方法,类似option的用法。*/
//在 Java 8 之前,流中不能有null值。它会导致NullPointerException.
// 从 Java 9 开始,Stream.ofNullable()方法允许您创建一个单元素流,该流包装一个不为null的值,否则为空流。
Stream.ofNullable(alphabets2).collect(Collectors.toList());
/*** 在 Stream 增强之外,还增强了 Optional ,Optional 增加了可以转换成 Stream 的方法。*/
Stream s = Optional.of(1).stream();
s.forEach(System.out::print);
- 多版本Jar文件
- @Deprecated 注解更改
/*** 从 Java 9 开始,@Deprecated注解将具有两个属性,即forRemoval和since.
* @eprecated forRemoval – 指示带注释的元素是否会在未来版本中被删除。
* since - 它返回注释元素被弃用的版本。
*/
@Deprecated(forRemoval = true,since = "jdk9")
- 模块化
JPMS(Java Platform Module System)是Java 9发行版的核心亮点。
JDK 9 附带了大约 92 个模块(在 GA 版本中可以进行更改)。Java 9 Module System有一个"java.base"模块。它被称为基本模块。它是一个独立的模块,不依赖于任何其他模块。默认情况下,所有其他模块都依赖于"java.base"。
个各模块通常只是一个 jar 文件,在根目录下有一个文件module-info.class。
要使用模块,请将 jar 文件包含到modulepath而不是classpath. 添加到类路径的模块化 jar 文件是普通的 jar 文件,module-info.class文件将被忽略。
- 接口私有方法
/**
* Java 8 允许在接口中编写默认方法,这是一个广受欢迎的功能。从 Java 9 开始,你可以在接口中包含私有方法
* 私有接口方法规则:
* 私有接口方法不能是抽象的
* 私有方法只能在接口内部使用。
* 私有静态方法可以在其他静态和非静态接口方法中使用。
* 私有非静态方法不能在私有静态方法中使用
*/
public interface Custom {
default int addEvenNumbers(int... nums) {
return add(n -> n % 2 == 0, nums);
}
default int addOddNumbers(int... nums) {
return add(n -> n % 2 != 0, nums);
}
// 私有的底层方法
private int add(IntPredicate predicate, int... nums) {
return IntStream.of(nums).filter(predicate);
}
}
JDK10
描述
- 自从 Java 9 开始,Oracle 调整了 Java 版本的发布策略,不再是之前的 N 年一个大版本,取而代之的是 6 个月一个小版本,三年一个大版本,这样可以让 Java 的最新改变迅速上线,而小版本的维护周期缩短到下个版本发布之前,大版本的维护周期则是 3 年之久。而 10 就是这么一个小版本,因为 Java 的后续版本基本都会包含之前新特性。
新语法
- JEP 286 var 局部类型推断
JEP 286 var 局部类型推断
让 Java 可以像 Js 里的 var 或者其他语言的 auto 一样可以自动推断数据类型。
这其实只是一个新的语法糖,底层并没有变化,在编译时就已经把 var 转化成具体的数据类型了,但是这样可以减少代码的编写。
让 Java 可以像 Js 里的 var 或者其他语言的 auto 一样可以自动推断数据类型。
var hashMap = new HashMap();
hashMap.put("a", "b");
var string = "hello java 10";
var stream = Stream.of(1, 2, 3, 4);
var list = new ArrayList();
GC
- G1 的自动切换并行收集
早在 Java 9 时就已经引入了 G1 垃圾收集器,G1 的优点很多。而在 Java 10 中还是做了小小调整,当G1 的并发收集线程不能快速的完成full GC 时,就会自动切换到并行收集,这可以减少在最坏情况下的 GC 速度。
也可以看出 G1的作者 其实在fullGC 的情况上处理 还在优化,因为他们设计G1的初衷 就是极力避免full GC的出现。但是当并发收集不能足够快地回收内存时,就会引发full GC 垃圾收集。G1的full GC的旧 实现 使用的单线程-标记-清理-压缩(a single threaded-mark-sweep-compact )算法。在 JEP 307中 作者决定采用 并行-清除-压缩(parallelize the mark-sweep-compact ) 算法 ,并使用与 Young 和Mixed 集合 相同数量的线程来进行处理。
线程数量可以通过命令 : -XX:ParallelGCThreads 来控制。(但是这也会影响我们的线程数量)
风险 设定: G1采用并发 收集fullGC的使用资源 肯定要比之前单线程 的时候更耗资源。
官文资料 :bugs.java.com/view_bug.do…
实验性:
- GraalVM首次引入
这是一个实验性的 JIT 编译器。Java 10 中最具有未来感的引入。
Graal 其实在 Java 9 中就已经引入了,它带来了 Java 中的 AOT (Ahead Of Time 提前编译)编译,还支持多种语言,如 Js、Python、Ruby、R、以及其他基于 JVM (如 Java、Kotlin)的和基于 LLVM (如 C、C++)的语言。
基本上由Java语言编写。
核心类库
- Collection新增方法 Collection.copyOf 复制得到一个不可改变集合
- Optional新增方法 orElseThrow 方法 value = null 直接抛异常。它是现有 get 方法的同义词,是现有 get 方法的首选替代方法。
- Stream 新增方法 转成不可变集合方法。
list.stream().collect(Collectors.toUnmodifiableList());
runtime
- 字节码生成已经得到改进,增强了 For 循环
在 for 循环之外声明迭代器变量可以在不再使用它时立即为其赋值 null。 这使得 GC 可以访问它,然后 GC 可以处理掉未使用的内存。当增强的 for 循环中的表达式是一个数组时,也会执行类似的操作。
衍生问题 研发者为何要在for循环后做该编译优化操作?
个人理解 是为了配合GC ,GC中的 Safe Point 安全点的设置。
安全点位置的选取基本上是以 “是否具有让程序长时间执行的特征” 为标准 进行选定的,因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长这样的原因而 长时间执行, “长时间执行” 的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转 等都属于指令序列复用,所以只有具有这些功能的指令才会产生安全点。
只有到达某些点才可以进行GC操作,这些点称作安全点(Safepoint),比如,循环的末尾、方法临返回前/调用方法的call指令后、可能跑异常的位置等。
安全点:在OopMap的协助下,HotSpot可以快速准确地完成GC Roots枚举,但随之而来的一个现实问题:可能导致引用关系变化,或者说导致OopMap内容变化的指令非常之多,如果为每一条指令都生成对应的OopMap,那将会需要大量的额外存储空间,这样垃圾收集伴随而来的空间成本就会非常高昂。
在实际上,也的确没有为每条指令都生成OopMap,只是在“特定的位置”记录 了这些信息,这些位置被称为安全点(Safepoint)。
有了安全点的设定,也就决定了用户程序执行时 并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须执行到达安全点后才能够暂停。
- 类数据共享
JVM 启动时有一步是需要在内存中加载类,而如果有多个 jar,加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间,为了减少这个时间,Java 10 引入了应用程序类数据共享(CDS)机制,它可以把你想共享的类共享在程序之间,使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。
JDK11
描述
- Java 11 是 Java 8 之后的第一个 LTS(长期支持) 版本。
核心类库 - String API的改动
/*** =================String 新增api功能========================================*/
String str= "aa\nbb";
// 判空,blank
System.out.println(str.isBlank());
// 该方法是根据 换行符 \n 或者回车 \r 或者回车换行 \r\n 进行分割
Stream lines = str.lines();
lines.forEach(System.out::println);
// 复制字符串
String str1= "abc";
String repeat = str1.repeat(2);
System.out.println(repeat);/*** 输出* false* aa* bb* abcabc*/
// 去除前后空白
String strip = " string字符 ";
System.out.println("==" + strip.trim() + "==");
// 去除前后空白字符,如全角空格,TAB
System.out.println("==" + strip.strip() + "==");
// 去前面空白字符,如全角空格,TAB
System.out.println("==" + strip.stripLeading() + "==");
// 去后面空白字符,如全角空格,TAB
System.out.println("==" + strip.stripTrailing() + "==");
// 输出
// == string字符 ==
// ==string字符==
// ==string字符 ==
// == string字符==
/**这里注意,trim 只能去除半角空格,而 strip 是去除各种空白符。*/
- File API改动
/**=================* File API改动* 读写文件变得更加方便。* ========================================*/
// 创建临时文件
Path path = Files.writeString(Files.createTempFile("test", ".txt"), "https://www.baidu.com");
System.out.println(path);
// 读取文件
String ss = Files.readString(Path.of("file.json"));
String s = Files.readString(path);
System.out.println(s);
- HTTP Client
/**=================* HTTP Client* 在 Java 11 中 Http Client API 得到了标准化的支持。且支持 HTTP/1.1 和 HTTP/2 ,
* 也支持 websockets。* http://openjdk.java.net/groups/net/httpclient/recipes-incubating.html
* HTTPClient 已经在 Java11中标准化了。
* * ========================================*/
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create("https://www.weibo.com")).build();
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).thenAccept(System.out::println).join();
// 同步
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
新语法
- Lambda 参数的局部变量语法
/**=================** Lambda 局部变量推断
* 集合jdk10引入的var语法,在jdk11的时候,这个语法糖可以在Lambda中进行使用。
* ** ========================================*/
var hashMap = new HashMap();
hashMap.put("A", "a");
hashMap.put("B", "b");
// 这里需要注意的是,(var k,var v) 中,k 和 v 的类型要么都用 var ,要么都不写,要么都写正确的变量类型。而不能 var 和其他变量类型混用。
hashMap.forEach((var k, var v) -> {
System.out.println(k + ": " + v);
}});
runtime
- 单命令运行 Java
/*** 单命令运行 Java* 之前运行一个java 程序
* 1. javac 编译字节码
* 2. java 运行程序class文件
* jdk11之后
* $ java xxx.java 即可运行
* */
实验性
- 可伸缩的低延迟垃圾收集器 - 引入ZGC垃圾收集器 jdk11提出而已 仅支持在 Linux/x64上使用。
- A No-Op (理解成无操作)垃圾收集器 -Epsilon GC
Hotspot 编译器 - 编译器线程的延迟分配
在默认打开的分层编译模式下,VM 在具有许多 CPU 的系统上启动大量编译器线程,而不管可用内存和编译请求的数量如何。因为线程即使在空闲时(几乎总是空闲)也会消耗内存,这导致资源的使用效率低下。
为了解决这个问题,实现进行了更改,以便在启动期间只启动每种类型的一个编译器线程,并动态处理进一步线程的启动和关闭。它由一个新的命令行标志控制,默认情况下是打开的。
JDK12
核心类库
- 文件对比 Files.mismatch
/*** 文件对比 Files.mismatch* 对比两个文件内容是否一致,如果内容一致,会返回 -1 ,如果内容不同,会返回不同的字节开始位置。*/
// 创建两个临时文件
Path aFile = Files.createFile(Paths.get("A.txt"));
Path bFile = Files.createFile(Paths.get("B.txt"));
// 写入相同内容
Files.write(aFile,"123456".getBytes(), StandardOpenOption.WRITE);
Files.write(bFile,"123456".getBytes(), StandardOpenOption.WRITE);
long mismatch = Files.mismatch(aFile, bFile);
System.out.println(mismatch);
// 追加不同内容
Files.write(aFile,"789".getBytes(), StandardOpenOption.APPEND);
Files.write(bFile,"987".getBytes(), StandardOpenOption.APPEND);
mismatch = Files.mismatch(aFile, bFile);
System.out.println(mismatch);
// 删除创建的文件
aFile.toFile().deleteOnExit();
bFile.toFile().deleteOnExit();
//输出//-1 相同//6 6下标 从0开始的话的 也就是第七位开始不同 正好是上面追加的点
- 数据格式处理NumberFormat
/**
* 紧凑数字格式化的支持 NumberFormat 添加了对紧凑形式的数字格式化的支持。紧凑型数字格式是指以简短或人类可读的形式表示数字。
* 例如,在 en _ US 区域设置中,根据 NumberFormat 指定的样式,
* 1000可以格式化为“1K”,1000000可以格式化为“1M”。风格。
* 紧凑数字格式由 LDML 的紧凑数字格式规范定义
* */
System.out.println("Compact Formatting is:");
NumberFormat upvotes = NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.SHORT);
System.out.println(upvotes.format(100));
System.out.println(upvotes.format(1000));
System.out.println(upvotes.format(10000));
System.out.println(upvotes.format(100000));
System.out.println(upvotes.format(1000000));
// 设置小数位数
upvotes.setMaximumFractionDigits(1);
System.out.println(upvotes.format(1234));
System.out.println(upvotes.format(123456));
System.out.println(upvotes.format(12345678));
/**
* Compact Formatting is:
* 100
* 1K
* 10K
* 100K
* 1M
* 1.2K
* 123.5K
* 12.3M
*/
GC
- ZGC现在支持类卸载。
通过卸载未使用的类,可以释放与这些类相关的数据结构,从而降低应用程序的总体占用。
ZGC 中的类卸载同时进行,而不会停止 Java 应用程序线程的执行,因此对 GC 暂停时间没有影响。
默认情况下启用此特性,但可以使用命令行选项 -XX:-ClassUnload 禁用此特性。
预览功能
- Switch 表达式 改进 case “A”, “B”, “C” -> “method”; 的形式,让Switch的代码编写变得更加优雅。
当case穿透开始时,后续的case就会失去匹配效果,内部的语句都会被执行,直到遇到break语句跳出switch,或者将整体switch语句执行完毕,才会结束。
jdk12之前版本
Scanner sc = new Scanner(System.in);
System.out.println("请输入星期几:");
int week = sc.nextInt();
String str = "";
switch (week) {
case 1:
case 2:
case 3:
case 4:
case 5:
str = "工作";
break;
case 6:
case 7:
str = "休息";
break;
default:
str = "输入有误";
break;
}
System.out.println(str);
/**
* 请输入星期几:
* 2
* 工作
*/
jdk12之后
Scanner sc = new Scanner(System.in);
System.out.println("请输入星期几:");
int week = sc.nextInt();
String str = switch (week) {
case 1, 2, 3, 4, 5 -> "工作";
case 6, 7 -> "休息";
default -> "输入有误";
};
System.out.println(str);
/**
* 请输入星期几:
* 7
* 休息
*/
删除项
- finalize 方法的删除
从 FileInputStream 和 FileOutputStream 中删除 finalize 方法, finalize 方法在 JDK9中不被推荐删除。jdk12中finalize方法被正式移除 。
删除 java.util 中的 finalize 方法。
未完待续!