admin管理员组

文章数量:1649178

聊聊Java8之后的JDK升级内容(看这一篇就够了)

    • 背景
    • 从 JDK 8 到 JDK 17 的新特性
      • JDK8 回顾
      • JDK9
      • JDK10
      • JDK11
      • JDK12
      • JDK13
      • JDK14
      • JDK15
      • JDK16
      • JDK17
    • JVM重要特性详解
      • GC变化
      • 吞吐量测试结果
      • 延迟测试结果
      • 暂停时间测试结果
      • 资源占用测试结果
    • GC总结
    • 升级必要性
    • 学习目的
    • 资料
    • 参考文献

背景

2022年Spring6和 SpringBoot3相继推出,在此之前,Java社区一直是"新版任你发,我用Java 8",不管新版本怎么出,很少有人愿意升级。

这一次,Spring 直接来了个大招,SpringBoot3和Spring6的最低依赖就是JDK17。 跨过 JDK 8-16,直接升级到 JDK 17。那么为什么是 JDK 17呢?

这么多新版本的 JDK,而且2022年还会推出 JDK 18 和 JDK 19,为什么 Spring 选择了 JDK 17呢。

主要是因为他是一个 Oracle官宣可以免费商用的LTS版本,所谓 LTS,是 Long Term Support,也就是官方保证会长期支持的版本。

下表是 Oracle 官方提供的 JDK 支持计划:

可以看得到,JDK 17 最多可以支持到 2029 年 9 月份。按照技术更新迭代的速度,这次免费商用 8 年可谓是良苦用心,为的就是让使用者放心大胆地将 JDK 升级到 JDK 17(不过JDK 8 支持的时间更长,可以延长到 2030 年 12 月,JDK8可谓是YYDS!)

现在,这种平衡或将被打破。因为 Java 届的霸主框架 SpringBoot,选择了最小支持的 Java LTS 版本,就是最新的 Java 17。

那么接下来,让我们看看从 JDK 8 到 JDK 17,Java 社区 8 年努力的成果有哪些?

从 JDK 8 到 JDK 17 的新特性

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 消除永久代 https://openjdk/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 https://openjdk/jeps/291
    为何要弃用CMS?

    (G1)垃圾收集器完全替代 CMS 的大多数用法。
    废弃并发标记清除(CMS)垃圾收集器,以便在将来的主要版本中停止对它的支持。
    研发者动机和目标:加速 HotSpot 中其他垃圾收集器的开发。减少 GC 代码库的维护负担,并加速新的开发。

核心类库

  • HTTP/2客户端

Java 9 引入了新的 API,它使用起来更干净、更清晰,并且还增加了对 HTTP/2 的支持。

  • Collection(集合) API更新
//在 Java 9 中为集合的创建增加了静态工厂创建方式,也就是 of 方法,通过静态工厂 of 方法创建的集合是只读集合,里面的对象不可改变。
        List<String> namesList = List.of("Lokesh", "Amit", "John");
        Set<String> namesSet = Set.of("Lokesh", "Amit", "John");
        Map<String, String> namesMap = Map.ofEntries(
                         Map.entry("1", "Lokesh"),
                         Map.entry("2", "Amit"),
                         Map.entry("3", "Brian"));
  • Stream(流) API改进

Java 9 引入了两种与 Streams 交互的新方法,即takeWhile/dropWhile方法。

	/**
     * 新方法takeWhile。dropWhile允许开发者基于谓词获取流的一部分
     */
    public static void main(String[] args) {

        List<String> alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
        List<String> one = alphabets
                .stream()
                .takeWhile(s -> !s.equals("d"))  //takeWhile: 从头开始筛选,遇到不满足的就结束了。
                .collect(Collectors.toList());
        //打印出:[a, b, c]
        System.out.println(one);

        List<String> alphabets2 = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
        List<String> subset2 = alphabets2
                .stream()
                .dropWhile(s -> !s.equals("d")) //odropWhile: 从头开始删除,遇到不满足的就结束了。
                .collect(Collectors.toList());
        //打印出:[d, e, f, g, h, i]
        System.out.println(subset2);


        /**
         * TODO 爆赞~~~~ 好用
         * 此外,它还添加了两个重载方法,即ofNullable和iterate方法,类似option的用法。
         */
        //在 Java 8 之前,流中不能有null值。它会导致NullPointerException.
        // 从 Java 9 开始,Stream.ofNullable()方法允许您创建一个单元素流,该流包装一个不为null的值,否则为空流。.
        Stream.ofNullable(alphabets2).collect(Collectors.toList());

        /**
         * 在 Stream 增强之外,还增强了 Optional ,Optional 增加了可以转换成 Stream 的方法。
         */
        Stream<Integer> 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)
                .sum();
    }

}

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<String, String>();
    hashMap.put("a","b");
    var string = "hello java 10";
    var stream = Stream.of(1, 2, 3, 4);
    var list = new ArrayList<String>();
    //编译后一样的
    /**
     *         HashMap<String, String> hashMap = new HashMap();
     *         hashMap.put("a", "b");
     *         String string = "hello java 10";
     *         Stream<Integer> stream = Stream.of(1, 2, 3, 4);
     *         new ArrayList();
     *
     *         缺点也很明显:
     *         一眼并不能看出 result 的数据类型。
     */

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的使用资源 肯定要比之前单线程 的时候更耗资源。
官文资料 :https://bugs.java/view_bug.do?bug_id=JDK-8172890

实验性:

  • 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 复制得到一个不可改变集合
    不可变工具集合是为何不可变嘞?
 		List<String> listCopy = List.copyOf(list);
        //Arrays.asList()
        Optional.of(list).orElseThrow();

        /**
         * 不可变工具集合是为何不可变嘞?
         * ListN  实体  ImmutableCollections.ListN  内部类
         * 类似Arrays.asList()方法  返回的不是我们认为list对象 而是一个内部类。
         */
  • Optional新增方法 orElseThrow 方法 value = null 直接抛异常。它是现有 get 方法的同义词,是现有 get 方法的首选替代方法。
  • Stream 新增方法 转成不可变集合方法。

list.stream().collect(Collectors.toUnmodifiableList());

runtime

  • 字节码生成已经得到改进,增强了 For 循环
 		/**
         * 编译后:
         * Iterator i$ = data.iterator();
         * for (; i$.hasNext(); ) { String b = (String)i$.next(); } b = null; i$ = null; }
         * *//*
        Iterator i$ = data.iterator();
        for (; i$.hasNext(); ) {
            String b = (String) i$.next();
        }
        b = null;
        i$ = null;
        }*/

这行编译后的代码有什么特点 ?
在 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<String> 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");
        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/groups/net/httpclient/recipes-incubating.html
         * HTTPClient 已经在 Java11中标准化了。
         * ========================================*/

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://www.weibo"))
                .build();
        // 异步
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println)
                .join();

        // 同步
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());

新语法

  • Lambda 参数的局部变量语法
   		/**=================
         *
         * Lambda 局部变量推断
         * 集合jdk10引入的var语法,在jdk11的时候,这个语法糖可以在Lambda中进行使用。
         *
         * ========================================*/
        var hashMap = new HashMap<String, Object>();
        hashMap.put("A", "a");
        hashMap.put("B", "b");
        hashMap.forEach((var k, var v) -> {
            System.out.println(k + ": " + v);
        });
        //这里需要注意的是,(var k,var v) 中,k 和 v 的类型要么都用 var ,要么都不写,要么都写正确的变量类型。而不能 var 和其他变量类型混用。

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));
        //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的代码编写变得更加优雅。

删除项

  • finalize 方法的删除

从 FileInputStream 和 FileOutputStream 中删除 finalize 方法, finalize 方法在 JDK9中不被推荐删除。jdk12中finalize方法被正式移除 。
删除 java.util 中的 finalize 方法。

JDK13

预览功能

  • Switch 表达式二次预览
  • 文本块首次预览
 		/**
         * 文本块 (jdk13 预览功能 )
         * 之前string中放入json 字符串 会出现大量的转义符,从 Java 13 开始你可以使用文本块的方式定义字符串了。
         * 向 Java 语言添加文本块。它避免了大多数转义序列的需要,自动以可预测的方式格式化字符串,并在需要时让开发人员控制格式。
         */
        String oldStr = "{\"name\":\"hh\",\"age\":18}";
        //终于不用写丑陋的长字符串了~~~~~~
        String str13= """
                {"name":"hh","age":18}
                """;

核心类库

  • 重新实现 Socket API
 		/**
         *  重新实现 Socket API
         *
         * java.Socket 和 java.ServerSocket 类早在 Java 1.0 时就已经引入了,
         * 它们的实现的 Java 代码和 C 语言代码的混合,维护和调试都十分不易;而且这个实现还存在并发问题,有时候排查起来也很困难。
         *
         * 底层 采用 NioSocketImpl 新的实现类 替代PlainSocketImpl
         *
         * 作者支持回退 -Djdk.usePlainSocketImpl 命令可以切回旧的实现。 还是很有考虑到的。
         */

        ServerSocket serverSocket = new ServerSocket(8000);
        Socket clientSocket = serverSocket.accept();
        // jdk 13 之后  底层类记载的是  sun.PlatformSocketImpl
        // jdk 13 之前  底层类记载的是  java.PlainSocketImpl

实验性

  • ZGC,归还未使用的内存

JEP 351: ZGC https://openjdk/jeps/351
在 Java 13 之前,ZGC 虽然在清理内存时导致的停顿时间非常少,但是即使内存已经长时间没有使用,ZGC 也不会将内存返还给操作系统,这对那些十分关注内存占用的应用程序非常不友好。
默认情况下启用此特性,但可以使用 -XX:-ZUncommit 显式禁用此特性。此外,内存不会被归还,因此堆大小会缩小到最小堆大小(- Xms)以下。这意味着,如果将 最小堆大小(- Xms)配置为等于最大堆大小(- Xmx),则将 隐式禁用 此特性。
-XX:ZUncommitDelay= (默认为300秒) 可以通过这个命令来控制 内存多少秒后 可以被归还。

GC

  • ZGC的新增功能
    添加 -XXSoftMaxHeapSize 标志

设置后,GC 将努力不使堆增长超过指定的大小,除非 GC 决定有必要这样做以避免 OutOfMemoryError。
不允许将软最大堆大小设置为大于最大堆大小(- Xmx)的值。如果未在命令行上设置,则默认为等于最大堆大小的值。

  • ZGC 支持的最大堆大小从4TB 增加到16TB。

JDK14

预览功能

  • instanceof 类型判断 (第一次预览)
  • Records class (第一次预览) 添加了一个新的 java.lang 类
  • 文本块 (二次预览)

实验性

  • Windows 上的 ZGC(实验性)
  • macOS 上的 ZGC(实验性)

新语法

  • 更有用的npe提示
 		/**
         * 更有用的 NullPointerExceptions提示  TODO 超级赞~~~
         */

        String str = "str";
        String ex = null;
        int length = str.length() + ex.length();
        System.out.println(length);
        //14 之前输出
        //Exception in thread "main" java.lang.NullPointerException
        //at dxt.learn.example.jdk14.JDK14Main.main(JDK14Main.java:36)

        //14 之后输出
        //Exception in thread "main" java.lang.NullPointerException:
        //	Cannot invoke "String.length()" because "ex" is null
        //at dxt.learn.example.jdk14.JDK14Main.main(JDK14Main.java:36)
  • Switch 表达式 正式发行
 		/**
         * Switch 表达式 (正式发行)
         * 也是很赞的功能  不用那么累赘的书写switch了
         *
         */

        // 通过传入月份,输出月份所属的季节  jdk 12的第一次预览
        String month12 = "may";
        String season12 = switch (month12) {
            case "march", "april", "may"            -> "春天";
            case "june", "july", "august"           -> "夏天";
            case "september", "october", "november" -> "秋天";
            case "december", "january", "february"  -> "冬天";
            default -> "month erro";
        };

        //jdk 13的第二次预览
        String month13 = "may";
        String season13 = switch (month13) {
            case "march", "april", "may":
                yield "春天";
            case "june", "july", "august":
                yield "夏天";
            case "september", "october", "november":
                yield "秋天";
            case "december", "january", "february":
                yield "冬天";
            default:
                yield "month error";
        };
        //jdk14 让这些 预览功能正式 发行  无需通过命令来开启功能

删除项

  • JEP 363: 移除 CMS 垃圾收集器

移除对 CMS(Concurrent Mark Sweep) 垃圾收集器的支持,其实早在 Java 9 就开始移除 CMS 垃圾收集器了,只不过在 Java 14 中被正式删除。

弃用

  • 弃用 ParallelScavenge + SerialOld GC 组合 https://openjdk.java/jeps/366,

由于使用场景不多,维护工作太大,废弃之。

  • 不推荐线程挂起/恢复

java.lang. Thread 和 java.lang. ThreadGroup 中的suspend() 和.resume()方法废弃。

JDK15

预览

  • Sealed Classes 密封类(二次预览)
  • instanceof 类型匹配 (二次预览)
  • Records class (二次预览)

新语法

  • 文本块 (正式发布) JDK13提出~JDK15发行
 		/**
         * 文本块 (正式发布)
         */
        String oldStr = "{\"name\":\"hh\",\"age\":18}";
        //终于不用写丑陋的长字符串了
        String str15= """
                {"name":"hh","age":18}
                """;
  • TreeMap 新的方法

putIfAbsent、computeIfAbsent、computeIfPresent、compute、merge

JVM

  • 禁用和废弃偏向锁(Biased Locking)

synchronized 同步时,有一套锁的升级机制,其中有一个锁机制就是偏向锁。
然而通过目前的 Java 开发环境来看,使用这些被 synchronized 同步的类的机会并不多,如开发者更喜欢使用 HashMap 或者 ArrayList 而非 HashTable 和 Vector。
性能提升程度和使用次都不太有用,而数偏向锁的引入增加了 JVM 的复杂性。
偏向锁被默认禁用,我们仍然可以使用 -XX:+UseBiasedLocking 启用偏向锁定,但它会提示 这是一个已弃用的 API。
偏向锁定可能会影响显示大量无争用同步的应用程序的性能。

GC

  • ZGC: 可扩展低延迟垃圾收集器(正式发布)

JDK11首次被引入 JDK15被正式发布.期间做过很多功能更新。启用命令: -XX:+UseZGC

JDK16

新语法

  • instanceof 模式匹配 (正式发行)
     	Object obj = new String();
        /**
         *  instanceof 类型匹配  正式发行 todo 也是好用的
         */
        if (obj instanceof String) {
            String s = (String) obj;
        }
        //当前的写法
        if (obj instanceof String s) {
            s.length();
            // Let pattern matching do the work!
            //.....
        }

        //public final boolean equals(Object o) {
        //    return (o instanceof Point other)
        //        && x == other.x
        //        && y == other.y;
        //}

  • Record Classes 记录类 (正式发行)
/**
 * JDK.16  JEP 395   https://openjdk/jeps/395
 *
 * 特点:
 *      Record是 Java 语言中的一种新类。
 *      它们充当不可变数据的透明载体,不像普通类那样拘谨。
 *      记录类有助于以比普通类更少的形式对普通数据聚合进行建模。
 * 动机:
 *    常见的抱怨是“Java 太冗长”或“太多仪式”。
 *     正确编写这样的数据载体类涉及大量低价值、重复、容易出错的代码:构造函数、访问器、equals、hashCode、toString等。
 *
 * 语法写法:
 *          写法:
 *          public record Teacher(String name, String className) {
 *
 *          }
 *
 *          编译后:
 *
 *          public record Teacher(String name, String className) {
 *           //隐式属性被final修饰
 *          private final String name;
 *          private final String className;
 *
 *
 *          Point(String name, String className) {
 *                  this.name= name;
 *                  this.className = className;
 *             }
 * }
 *
 */
@Slf4j
public record Teacher(String name, String className) {
    public String sayHi(){
        log.info("a");
        return "a";
    }

    /**
     * 本地记录类嵌套记录类的一个特例
     * 在以下示例中,商家和月销售数据的聚合使用本地记录类 建模MerchantSales。使用这个记录类提高了以下流操作的可读性:
     *
     * List<Merchant> findTopMerchants(List<Merchant> merchants, int month) {
     *
     *     // Local record 本地记录类
     *     record MerchantSales(Merchant merchant, double sales) {}
     *
     *     return merchants.stream()
     *         .map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
     *         .sorted((m1, m2) -> Doublepare(m2.sales(), m1.sales()))
     *         .map(MerchantSales::merchant)
     *         .collect(toList());
     * }
     */

  public static void main(String[] args) {
      Teacher teacher = new Teacher("b","c");
      Class<? extends Teacher> aClass = teacher.getClass();
      System.out.println(aClass);
      /**
       * 两个新增的反射方法
       */
      RecordComponent[] recordComponents = aClass.getRecordComponents();
      System.out.println(aClass.isRecord());
      System.out.println(User.class.getSuperclass());


  }


}

  • Add Stream.toList() Method
  		/**
         *         ==============
         *         Add Stream.toList() Method  TODO 好用~~
         *`stream.collect(Collectors.toList())` 和 `stream.collect(Collectors.toSet())`
         * 最受欢迎的方法引来的简化和封装 。
         *
         *         ====================
         */
        record  User(String name){}
        List<User> users = new ArrayList<>();
        List<String> strings = users.stream().map(User::name).toList();
        // 等价 users.stream().map(User::name).collect(Collectors.toList());

预览

  • Sealed Classes(密封类)二次预览

GC

  • ZGC 并发栈处理 JEP 376. https://openjdk/jeps/376

ZGC 现在可以并发地处理线程堆栈。
这允许 ZGC 在并发阶段处理 JVM 中的所有根,而不需要STW
ZGC 暂停中完成的工作量现在变成了常量,通常不超过几百微秒。

Runtime

  • 弹性元空间

-XX:MetaspaceReclaimPolicy=(balanced|aggressive|none) 来控制
默认balanced:均衡会导致 VM 回收内存,同时保持最小的计算开销
aggressive:增加回收的速度,代价是更昂贵的记账

弃用

  • AOT 和 Graal JIT 实验特征的去除

AOT 和 JIT 编译器已在 JDK 16 中移除,本次在 JDK17 源代码中移除。

JDK17

描述

  • Java 17 在 2021 年 9 月 14 日正式发布,Java 17 是一个长期支持(LTS)版本。

新语法

  • Sealed Classes 在 Java SE 15 中首次预览,是此版本中的永久功能。
/**
 * JDK17
 * 密封类 :密封的类或接口只能由允许这样做的类和接口扩展或实现。
 * sealed 修饰符  密封类必须有子类
 * permits 指定许可
 *
 * 作者设计动机:
 *      限制一个接口或者一个类的扩展。
 *      超类应该可以被广泛访问,但不能被广泛扩展。
 *
 * JDK中的应用示例:AbstractStringBuilder  是以包私有的形式,两个子类:StringBuffer、StringBuilder。
 *
 * 密封类可类似枚举使用在Switch中作为case条件进行判断,无需进项 instance of 的语句判断并转换。
 */
public sealed class BaseObj permits BaseObj.SunObj {

    final class SunObj extends BaseObj implements BaseApi {

        @Override
        public void sayHi() {

        }
    }

    record  SunBo() implements BaseApi{

        @Override
        public void sayHi() {

        }
    }
    //应用在java.long.constant包中ConstantDesc

/**
 * JDK17
 * 密封类
 * sealed 修饰符  密封类必须有子类
 * permits 指定许可
 */
public sealed class BaseObj1  {
    //这种声明写法  可以省略掉  permits关键字
    //编译器可以自己识别推断出允许的子类
    //编译器可以判断出 baseObj1对象 允许三个sunObj的子类
    final class SunObj1 extends BaseObj1 {

    }

    final class SunObj2 extends BaseObj1 {

    }

    final class SunObj3 extends BaseObj1 {

    }
}

核心类库

  • 特定于上下文的反序列化过滤器
  • 增强的伪随机数生成器
/**
 *增强的伪随机数生成器 Enhanced Pseudo-Random Number Generators
 */
public class RandomMain {

/*
    public static void main(String[] args) {
        ThreadLocalRandom randomGenerator = ThreadLocalRandom.current();
        System.out.println(randomGenerator.nextInt(10));
    }
*/
    //基础用法
    public static void main(String[] args) {

        //基础用法
        RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
        // 使用时间戳作为随机数种子
        RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            System.out.println(randomGenerator.nextInt(10));
        }
        //你也可以遍历出所有的 PRNG 算法。
        RandomGeneratorFactory.all().forEach(factory -> {
            System.out.println(factory.group() + ":" + factory.name());
        });

        //System.out.println:
        //LXM:L32X64MixRandom
        //LXM:L128X128MixRandom
        //LXM:L64X128MixRandom
        //Legacy:SecureRandom
        //LXM:L128X1024MixRandom
        //LXM:L64X128StarStarRandom
        //Xoshiro:Xoshiro256PlusPlus
        //LXM:L64X256MixRandom
        //Legacy:Random
        //Xoroshiro:Xoroshiro128PlusPlus
        //LXM:L128X256MixRandom
        //Legacy:SplittableRandom
        //LXM:L64X1024MixRandom

        /**
         * 可以看到 Legacy:Random 也在其中,新的 API 兼容了老的 Random 方式,
         * 所以你也可以使用新的 API 调用 Random 类生成随机数。
         */
        // 使用 Random
        RandomGeneratorFactory<RandomGenerator> Random = RandomGeneratorFactory.of("Random");
        // 使用时间戳作为随机数种子
        RandomGenerator random = Random.create(System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            System.out.println(random.nextInt(10));
        }


    }


}

其他改变

  • 更强封装 JDK 内部
  • 恢复始终严格的浮点语义
 	/**
     *恢复始终严格的浮点语义
     */
    public static void main(String[] args) {
        testStrictfp();
    }

    public  strictfp   static void testStrictfp() {
        System.out.println(255555555f - 255555554f);
        System.out.println(25555555f - 25555554f);
        System.out.println(1.7 - 1.6);
        double a = 0.05;
        double b = 0.01;
        double sum =a+b;
        System.out.println(sum);
        //这样的结果如果是在银行或者其他商业计算中出现那么结果是不能想像的。所以有严格的浮点计算。JAVA对这样的计算是封装在BigDecimal类中的。
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~");
        //官方的例子:
        double d = 8e+307;
        System.out.println(4.0 * d * 0.5);
        System.out.println(2.0 * d);
        //Double.MAX_VALUE大概是1.8e+308
        /**
         *
         * 扩展指数范围
         * 体会一下,也就是在某种情况下,数字的范围不一样。并不限定于32位float和64位double了。上面的计算结果,可能就不是Infinity,而是1.6E308或其他值。
         * 一旦使用了strictfp来声明一个类、接口或者方法时,那么所声明的范围内Java的编译器以及运行环境会完全依照浮点规范IEEE-754来执行。
         *
         * IEEE-754到底是个什么东东呢?
         * IEEE二进制浮点数算术标准(IEEE 754)是最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。  http://zh.wikipedia/wiki/IEEE_754\
         * 大致意思是:在一个FP-strict表达式中间的所有值必须是float或者double,这意味着运算结果也肯定是IEEE754中所规定的单精度和双精度数,也即是32位的float和64位的double。
         * 这里也只说明了,float和double的规定,是符合IEEE754标准的。
         *
         * 1.strictfp翻译为“精确的浮点”不够贴切,容易引起误解。
         *
         * 2.strictfp关键字的使用与IEEE754没有直接因果关系。IEEE 754,是IEEE制定的,而不是J2EE标准:)
         *
         * 3.使用strictfp关键字的目的,是保证平台移植之后,浮点运算结果是一致的。
         */
    }

JVM重要特性详解

GC变化

  • JDK 9:设置 G1 为 JVM 默认垃圾收集器
  • JDK 10:并行全垃圾回收器 G1,通过并行 Full GC 改善 G1 的延迟。目前对 G1 的 full GC 的实现采用了单线程-清除-压缩算法。JDK 10 开始使用并行化-清除-压缩算法。
  • JDK 11:推出 ZGC 新一代垃圾回收器(实验性),目标是GC暂停时间不会超过 10 毫秒,既能处理几百兆的小堆,也能处理几个 T 的大堆。
  • JDK 14 :删除 CMS 垃圾回收器;弃用 ParallelScavenge + SerialOld GC 的垃圾回收算法组合;将 ZGC 垃圾回收器移植到 macOS 和 Windows 平台
  • JDk 15 : ZGC( JEP 377)和 Shenandoah( JEP 379)不再是实验性功能。默认的 GC 仍然是 G1。
  • JDK 16:增强 ZGC,ZGC 获得了 46 个增强功能和 25 个错误修复,控制 stw 时间不超过 10 毫秒

下面我们看看使用 16GB 堆内存和 SPECjbb® 20151 基准测试对前三个收集器的测试结果。

吞吐量测试结果


在吞吐量方面 (数值越高表示性能越好

Parallel 中 JDK 8 和 JDK 11 差距不大,JDK 17 相较 JDK 8 提升 15% 左右;

G1 中 JDK 17 比 JDK 8 提升 18%;

ZGC 在 JDK 11引入,JDK 17 对比 JDK 11 提升超过 20%。

延迟测试结果


延迟比较

在 GC 延迟方面,JDK 17 的提升更为明显。我们可以看到为缩短 GC 暂停时间所做的努力都得到了回报,很多提升都是因为 GC 的改进。

在 Parallel 中 JDK 17 对比 JDK 8 和JDK 11 提升 40%;

在 G1 中,JDK 11 对比 JDK 8 提升 26%,JDK 17 对比 JDK 8 提升接近 60%! G1最佳提升~~~

ZGC 中 JDK 17 对比 JDK 11 提升超过 40%。

暂停时间测试结果

暂停时间对比

我们可以看到 JDK 17 中的 ZGC 远低于目标:亚毫秒级的暂停时间。

G1 的目标是在延迟和吞吐量之间保持平衡,远低于其默认的目标:200 毫秒的暂停时间。

ZGC 的设计会保证暂停时间不随堆的大小而改变,我们可以清楚地看到当堆扩大到 128 GB 时的情况。

从暂停时间的角度来看,G1 比 Parallel 更善于处理更大的堆,因为它能够保证暂停时间满足特定目标。

资源占用测试结果


资源占用

上图比较了三个不同收集器原生内存的使用峰值。由于从这个角度来看 Parallel 和 ZGC 都非常稳定,因此我们应该看一看原始数字。

我们可以看到 G1 在这方面确实有所改进,主要原因是所有功能和增强功能都提高了记忆集管理的效率 。

GC总结

  • 无论使用哪种收集器,与旧版本相比,JDK 17 的整体性能都有很大的提升。

  • 在 JDK 8 中,Parallel 是默认设置,但在 JDK 9 中改为了 G1。从那以后,G1 的改进速度就超过了 Parallel,但在有些情况下可能 Parallel 仍然是最佳选择。而 ZGC(JDK 15 正式使用)的加入,成为了第三种高性能替代方案。

升级必要性

  • Spring 带头猛冲,直接上 JDK 17。 如果 Spring 6 还支持 Java 8 的话,那很多技术框架都要跟着 Java 8 的兼容,与其这样不如由 Spring 带头,一起飞升 Java 17,不过有些框架还不支持 JDK 17。
  • 性能升级,光从 Java 8 换到 Java 11,啥也没干性能直接就提升了 10%(NIO 底层的重写),更何况一路到 JDK 17 过程中的 JVM 相关优化。不过光是性能的优化还不足以吸引企业进行JDK升级,毕竟加机器就能解决,费不着各种升级改造, 还可能有安全问题。
  • JDK 21 可能成为真正的经典版本。 目前还没有 Project loom 功能,代表着没有协程,性能方面比有协程 JDK 差远了。比如阿里开源的 JDK 8、11 就有非侵入式协程。Project loom 功能在 JDK 19 已经可预览了,Project loom 大概在 JDK 21 时正式推出,而JDK21 又是一个 LTS 版本 ,值得期待。

学习目的

为了升级JDK

  • 升级jdk是必然趋势,8-17的升级跨度太大,我们需要知道到底变化了哪些,有哪些优化是我们可以采用的。

感受jdk研发者的思路 感受java语言的进步

阅读源码

  • 现在开源框架的底层最低jdk版本越来越高,采用的语法糖越来越新,如果不学习新的jdk,那么对于我们阅读源码也越来越吃力。

新语法的好处

  • java语言常备吐槽的一点,就是语言臃肿。新的语法支持可以让我们轻便开发。随着jdk功能的强大我们的开发效率以及系统功能的稳定性都有所提升。简单的说 一个系统jdk的升级 就等于做了一次大的优化。

保持新鲜

  • 在升级后面临着大量的语法糖,当同事写了一段很简练的代码完成了功能后,对其语义无法理解。程序员是要保持新鲜的。

再好的总结都值得你再去看一遍 开发者文档 ~~~

资料

笔者整理的思维笔记:ProcessOn
新特性相关代码实例
Java各个版本的文档入口:https://docs.oracle/en/java/javase/index.html
Java各个版本下载:https://jdk.java/archive/

参考文献

https://baijiahao.baidu/s?id=1749917039132965540&wfr=spider&for=pc

本文标签: 这一内容就够了JDK