Java 与 JVM · 教程

不要再在用JDK11写JDK8的代码了!

本文写于 2021 年,涉及的组件版本、配置与数据可能已过时,请按经验材料阅读并核对当前版本。原文链接与内容保持不变。

入职新公司两个月了,用JDK11写了两个月JDK8的代码,再也憋不住想看看JDK11的新特性了。没用过JDK9,10. 那就说说JDK8-JDK11的变化吧。

新

  • 语法增强
    • 本地变量类型推断
    • Collection增强
    • Stream增强
    • Optional增强
    • InputStream增强
    • String增强
  • 模块化开发
  • 新工具
    • REPL交互式编程
    • Low-Overhead Heap Profiling
    • Flight Recorder
  • 新功能
    • 源代码直接执行
    • 完全支持Linux容器
    • 支持Unicode 10
    • 新支持的加密算法
    • HttpClient
  • 垃圾回收器
    • ZGC
    • Epsilon
    • 更好的G1
  • 移除与不再推荐使用的类库或功能
    • 移除了Java EE和CORBA Moudles
    • 将Nashorn Javascript标记为不推荐
    • 将Pack200 Tools and API标记为不推荐

JDK8-JDK11的语法新特性

本地变量类型推断

since JDK10

Java10以后可以用var定义一个局部变量,不用显式写出它的类型。但要注意,被var定义的变量仍然是静态类型,编译器会试图去推断其类型。所以,我们需要注意 1.不兼容的类型是不能重新赋值的! 2.只要编译器无法推断出变量类型,就会编译错误!

举个栗子:

基本使用

public static void test1() {
    var str1 = "local variable type interface";
    String str2 = "local variable type interface";
    System.out.println(str1 == str2); //true
}

简化泛型声明

/**
 * 简化泛型声明
 */
public static void test2() {
    var lists = new ArrayList<Map<String, List<String>>>();

    for (var item : lists) {
        var entries = item.entrySet();
        for (var entry : entries) {
            String key = entry.getKey();
            var values = entry.getValue();
            for (var value : values) {
                System.out.println(value);
            }
        }
    }
}

幸好编译器会有类型提示。如下图。

简化lambda参数

/**
 * lambda参数
 * 从Java 11开始,lambda参数也允许使用var关键字:
 */
public static void test3() {
    Predicate<String> predNotNull = (var a) -> a != null && !a.isBlank();

    String strAfterFilter = Stream.of("I", "", "love", " ", "my", "wife", null, "very", "much")
            .filter(predNotNull)
            .collect(Collectors.joining(" "));
    System.out.println(strAfterFilter); //I love my wife very much
}

不兼容的类型赋值

这种直接编译失败, 第一行和第二行已经推断出来了 str 是 String 类型。 第三行就不能赋值为double 类型了。

不能推测的类型

但是,对于用习惯了 IDEA快捷指令的我来说,这个var变量,对我来说毫无意义。感觉jdk是在炫技,但我毫无兴趣。

比如,在idea中,你输入 new ArrayList().var ,然后回车,IDEA就会为你自动生成类型。 类似的还有 lists.for, lists.fori 等等. 所以,个人并不推荐大家使用该语法,并非不能用,比如在 lambda 中使用替换类型时还是比较好的。如果你还没有掌握 xx.var 这种异能加持的话,想用就用吧。

总之,看别人写了个 var 时,不要发出 “哇藕,Java 还可以这么写”。

Collection增强

这个比较简单,就是 List , Set, Map 这三种集合多了两个方法 of 和 copyOf.

since jdk9

这里直接用三个栗子分别演示一下

List

List.of 内部是创建一个的 immutable collections。不可变集合。所以不可以增删改元素。

List.of() 和 List.copyOf() 都是创建的不可变集合

/**
 * 演示 List
 */
public static void test1() {
    List<Integer> integers = List.of(1, 2, 3, 4);
    List<Integer> integers1 = List.copyOf(integers);
    System.out.println(integers == integers1); // true
    integers.add(5); //  UnsupportedOperationException
}

Set

Set 和 List 的用法类似。 同样的,也是不可变集合。需要注意的是,copyOf方法,如果形参是可变集合,则返回的也是可变集合。

/**
 * 演示 Set
 */
public static void test2() {
    Set<Integer> integers = Set.of(1, 2, 3, 4);
    Set<Integer> integers1 = Set.copyOf(integers);
    System.out.println(integers == integers1); // true
//        integers.add(5); //  UnsupportedOperationException

    Set<Integer> integers2 = new HashSet<>();
    integers2.add(1);
    integers2.add(2);
    integers2.add(3);
    Set<Integer> integers3 = Set.copyOf(integers2);
    System.out.println(integers3 == integers2); // false

    // 元素不能重复 =>  java.lang.IllegalArgumentException
    Set<Integer> integers4 = Set.of(1, 2, 3, 4,3);
}

Map

/**
 * 演示Map
 */
public static void test3(){
    // 使用of创建Map
    Map<String, Integer> map1 = Map.of("k1", 2, "k2", 2);
    Map<String, Integer> map2 = Map.copyOf(map1);
    System.out.println(map1 == map2); // true

    // copyOf,取决于传入的map是否为可变集合
    HashMap<String, Integer> map3 = new HashMap<>();
    map3.put("k1",1);
    map3.put("k2",2);
    Map<String, Integer> map4 = Map.copyOf(map3);
    System.out.println(map3 == map4); //false

    // key不能重复 => java.lang.IllegalArgumentExceptio
    Map<String, Integer> map5 = Map.of("k1", 2, "k2", 2, "k1", 3);
}

Stream增强

since JDK9

Stream相关内容,新增了4个方法.

增加单个参数构造方法

/**
 * 新增单个元素的Stream构造,允许为空
 */
public static void demo1() {
    System.out.println(Stream.ofNullable(null).count()); // 0
    System.out.println(Stream.of(1).count()); //1
}

增加takeWhile方法

/**
  * 演示take while方法
  * <p>
  * 从头开始计算,遇到不满足Predicate的元素即停止。
  */
public static void demo2() {
    List<Integer> collect1 = Stream.of(1, 2, 3, 4, 5, 3, 2, 1).takeWhile(i -> i <= 3).collect(Collectors.toList());
    System.out.println(collect1); // [1, 2, 3]

    List<Integer> collect2 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1).takeWhile(i -> i > 6).collect(Collectors.toList());
    System.out.println(collect2); // [ ]
}

增加 dropWhile 方法

/**
  * 演示 dropWhile 方法
  * 从头开始计算,遇到第一个不满足 Predicte的元素时,开始计算。
  */
public static void demo3() {
    List<Integer> collect1 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1).dropWhile(i -> i <= 3).collect(Collectors.toList());
    System.out.println(collect1); // [1, 2, 3, 4, 5, 4, 3, 2, 1]

    List<Integer> collect2 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1).dropWhile(i -> i > 3).collect(Collectors.toList());
    System.out.println(collect2); // [4, 5, 4, 3, 2, 1]
}

iterate 重载方法

这个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代。

需要注意的是:相比JDK8的方法,iterator是第三个参数,第二个参数是Prediction。Prediction中进行判断的取值是迭代之后的数值。

/**
  * 演示 iterate 的重载方法
  */
public static void demo4() {
    // Jdk8支持使用这种方式, 生成从1开始的平方数
    List<Integer> collect1 = Stream.iterate(1, i -> i * 2).limit(5).collect(Collectors.toList());
    System.out.println(collect1); // [1, 2, 4, 8, 16]

    // JDK9 支持,加入一个 Prediction, 判断如何终止。
    List<Integer> collect2 = Stream.iterate(1, i -> i <= 16, i -> i * 2).limit(10).collect(Collectors.toList());
    System.out.println(collect2); // [1, 2, 4, 8, 16]
}

Optional 增强

Since JDK 9

/**
  * 提供一个默认的Optional对象
  */
private static void demo1() {
    // 定义一个变量, 可能为空 or not
    var variable = "jdk 11";

    // jdk 8 支持of(),ofNullable() 转换为Optional对象。
    List<String> collect = Optional.of(variable).stream().collect(Collectors.toList());

    // JDK 8 可以通过 orElse, 当 variable 为空的时候,返回一个默认的字符串值。
    Optional.ofNullable(variable).orElse("");

    // jdk 9 提供了提供 or() 返回一个默认Optional对象。
    Optional.empty().or(() -> Optional.of("jdk 11")).get();
}

InputStream 加强

InputStream 终于有了一个非常有用的方法:transferTo,可以用来将数据直接传输到 OutputStream,这是在处理原始数据流时非常常见的一种用法。

还是举个栗子吧:

/**
  * 直接将数据写入输出流
  */
public static void demo1() throws IOException {
    var classLoader = ClassLoader.getSystemClassLoader();
    var inputStream = classLoader.getResourceAsStream("readFile.txt");
    var javastack = File.createTempFile("writeFile", "txt");
    try (var outputStream = new FileOutputStream(javastack)) {
        inputStream.transferTo(outputStream);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

String增强

isBlank

判断目标字符串是否是空白字符。

使用功能上的问题,感觉还是例子来的舒服。

/**
  * 判断字符串是否为空
  */
private static void demo1() {
    // 半角空格  ===> true
    System.out.println(" ".isBlank());

    // 全角空格  ===> true
    System.out.println(" ".isBlank());

    // 半角空格的unicode字符值  ===> true
    System.out.println("\u0020".isBlank());

    // 全角空格的unicode字符值  ===> true
    System.out.println("\u3000".isBlank());

    // 制表符  ===> true
    System.out.println("\t".isBlank());

    // 回车  ===> true
    System.out.println("\r".isBlank());

    // 换行  ===> true
    System.out.println("\n".isBlank());

    // 各种空白字符拼接  ===> true
    System.out.println(" \t\r\n ".isBlank());
}

strip,stripLeading与stripTrailing

去除字符串的前后字符串

// 全角空格 + 制表符 + 回车 + 换行 + 半角空格 + <内容> + 全角空格 + 制表符 + 回车 + 换行 + 半角空格
var strTest = " \t\r\n 你好 jdk11 \t\r\n ";

// strip 去除两边空白字符
System.out.println("[" + strTest.strip() + "]");
// stripLeading 去除开头的空白字符
System.out.println("[" + strTest.stripLeading() + "]");
// stripTrailing 去除结尾的空白字符
System.out.println("[" + strTest.stripTrailing() + "]");

repeat

var strOri = "jdk11";
var str1 = strOri.repeat(1);
var str2 = strOri.repeat(3);
System.out.println(str1);
System.out.println(str2);
// repeat传入参数为1时,不会创建一个新的String对象,而是直接返回原来的String对象。
System.out.println(str1 == strOri);

lines

 /**
  * 按照换行符拆分字符串
  */
private static void demo3() {
    var strContent = "hello java\rhello jdk11\nhello world\r\nhello everyone";
    // lines方法用 \r 或 \n 或 \r\n 对字符串切割并返回stream对象
    strContent.lines().forEach(System.out::println);
    System.out.println(strContent.lines().count());
}

模块化开发

Java9引入了模块化,Java Platform Module System,java平台模块系统,简称JPMS。

这里和大家一起做一下。

新建两个 module

我们新建两个模块 core 和 business。 如下图

core 模块配置

  • 新建一个类 RestResult ,表示公共调用的类。
public class RestResult<T> {

    private T data;

    private Integer code;

    private String errMsg;

    public RestResult(T data) {
        this.data = data;
    }
}
  • 新建一个 module-info.java. 声明 module信息. exports 将 com.fxb.learn.module.core 这个包下的类都export出去。
    • 如果是一个java的普通应用,则在 src 目录下,新建 module-info.java 文件即可。
    • 如果是一个maven应用,则需要在 src/main/java 目录下,新建 module-info.java 文件。
// core 是module的名称
module core {
    // 将指定的包下类,export出去
    exports com.fxb.learn.module.core;
}

business 模块配置

  • 在business模块下,新建一个 module-info.java. 文件位置上core模块中的一致。
module business {
    requires core;
}
  • 在Idea中配置,business模块引用core模块。

写个例子看看

 /**
  * 演示模块调用。
  */
private RestResult<User> getUserById() {
    return new RestResult<>(new User(1, "fangjiaxiaobai"));
}

新工具

JDK还提供了一些新的工具, REPL交互式编程,Low-Overhead Heap Profiling(免费的低耗能飞行记录仪和堆分析仪),Flight Recorder(黑盒子)

REPL交互式编程

你是否使用 jupter, java 也有了!

Java提供了一个新的工具jshell,Java终于可以像python,scala等语言那样,交互式演示语法了

具体命令可以使用 /help 命令查看。

Low-Overhead Heap Profiling

免费的低耗能飞行记录仪和堆分析仪。

通过JVMTI的SampledObjectAlloc回调提供了一个开销低的heap分析方式提供一个低开销的,为了排错java应用问题,以及JVM问题的数据收集框架。 具有一下功能:

  • 提供用于生产和消费数据作为事件的API
  • 提供缓存机制和二进制数据格式
  • 允许事件配置和事件过滤
  • 提供OS,JVM和JDK库的事件

Flight Recorder

Flight Recorder 源自飞机的黑盒子。 Flight Recorder 以前是商业版的特性,在java11当中开源出来,它可以导出事件到文件中,之后可以用Java Mission Control 来分析。

两种启动方式:

可以在应用启动时配置java -XX:StartFlightRecording 应用启动之后,使用jcmd来录制,如下代码:

$ jcmd <pid> JFR.start  # 启动记录仪
$ jcmd <pid> JFR.dump.filename=recording.jfr  # 将记录内容保存到文件里
$ jcmd <pid> JFR.stop  # 停止记录仪

不过在 jdk11 是没办法查看 jfr 的。如果想看,安装 jdk12 吧。 不,可以试试 jdk16 , jdk16 也是LTS 版本!·

JFR是一套集成进入JDK、JVM内部的事件机制框架,通过良好架构和设计的框架,硬件层面的极致优化,生产环境的广泛验证,它可以做到极致的可靠和低开销。在SPECjbb2015等基准测试中,JFR的性能开销最大不超过1%,所以,工程师可以基本没有心理负担地在大规模分布式的生产系统使用,这意味着,我们既可以随时主动开启JFR进行特定诊断,也可以让系统长期运行JFR,用以在复杂环境中进行”After-the-fact”分析。还需要苦恼重现随机问题吗? JFR让问题简化了很多 在保证低开销的基础上, JFR提供的能力也令人眼前一亮,例如:我们无需BCI就可以进行Object Allocation Profiling, 终于不用担心BTrace 之类把进程搞挂了。对锁竞争、阻塞、延迟,JVM GC、SafePoint 等领域,进行非常细粒度分析。甚至深入JIT Compiler 内部,全面把握热点方法、内联、逆优化等等。JFR提供了标准的Java,C++ 等扩展API,可以与各种层面的应用进行定制、集成,为复杂的企业应用栈或者复杂的分布式应用,提供All-in-One 解决方案。而这一切都是内建在JDK和JVM内部的,并不需要额外的依赖,开箱即用。

新功能

HttpClient

JDK 9开始引入HttpClient API来处理HTTP请求。 从JDK 11开始,这个·正式进入标准库包。 参考网址:http://openjdk.java.net/groups/net/httpclient/intro.html

HttpClient具有以下特性:

  • 支持 HTTP1.1 和 HTTP2, websocket 协议
  • 支持同步和异步编程模型
  • 将请求和响应主体作为响应式流(reactive-streams)处理,并使用构建器模式
  • 要发送http请求,首先要使用其构建器创建一个HttpClient。这个构建器能够配置每个客户端的状态:
    • 首选协议版本 ( HTTP/1.1 或 HTTP/2 )
    • 是否跟随重定向
    • 代理
    • 身份验证

一旦构建完成,就可以使用HttpClient发送多个请求。

HttpRequest

HttpRequest是由它的构建器创建的。请求的构建器可用于设置:

  • 请求URI
  • 请求Method ( GET, PUT, POST )
  • 请求主体(如果有)
  • 超时时间
  • 请求头

HttpRequest构建之后是不可变的,但可以发送多次。

Synchronous or Asynchronous

请求既可以同步发送,也可以异步发送。当然同步的API会导致线程阻塞直到HttpResponse可用。异步API立即返回一个CompletableFuture,当HttpResponse可用时,它将获取HttpResponse并执行后续处理。

Data as reactive-streams

请求和响应的主体作为响应式流(具有非阻塞背压的异步数据流)供外部使用。HttpClient实际上是请求正文的订阅者和响应正文字节的发布者。BodyHandler接口允许在接收实际响应体之前检查响应代码和报头,并负责创建响应BodySubscriber。

HttpRequest和HttpResponse类型提供了许多便利的工厂方法,用于创建请求发布者和响应订阅者,以处理常见的主体类型,如文件、字符串和字节。这些便利的实现要么累积数据,直到可以创建更高级别的Java类型(如String),要么就文件流传输数据。BodySubscriber和BodyPublisher接口可以实现为自定义反应流处理数据。

HttpRequest和HttpResponse还提供了转换器,用于将 java.util.concurrent.Flow 的 Publisher/Subscriber 类型转换为 HTTP Client 的 BodyPublisher/BodySubscriber 类型。

请求协议 HTTP/2

Java HTTP Client支持 HTTP/1.1 和 HTTP/2。默认情况下,客户端将使用 HTTP/2 发送请求。发送到尚不支持 HTTP/2 的服务器的请求将自动降级为 HTTP/1.1。

以下是HTTP/2带来的主要改进:

  • 标头压缩。 HTTP/2 使用 HPACK 压缩,从而减少了开销。
  • 与服务器的单一连接减少了建立多个TCP连接所需的往返次数。
  • 多路复用。 在同一连接上,同时允许多个请求。
  • 服务器推送。 可以将其他将来需要的资源发送给客户端。
  • 二进制格式。 更紧凑。

由于HTTP/2是默认的首选协议,并且在需要的地方无缝地实现回退到HTTP/1.1,那么当HTTP/2被更广泛地部署时,Java HTTP客户端就无需修正它的应用代码。

具体的Java Doc 可以参考: https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/package-summary.html

看了 Java Doc, 感觉使用起来比较简单。这里就不举例了。感兴趣的朋友,可以自行深入研究一下。

源代码直接执行

一个单文件源代码,也就是说,单独的java文件,有main方法,且只依赖jdk类库以及自己文件内部定义的类,可以直接用java执行而无需先编译再执行编译后的class文件了。

你可能问了,有什么用呢? 我平时也不关系它是否生成了class文件。

如果你是做数据相关的工作,可能需要写一些脚本的时候,这却是轻松了很多。

➜  learn git:(master) ✗ ll
total 8
-rw-r--r--  1 wangxiyue  staff   411B Nov  9 22:58 SourceCodeExecDemo.java
➜  learn git:(master) ✗ java SourceCodeExecDemo.java
1.解析数据格式.....
2.处理数据.....
3.重组数据.....
➜  learn git:(master) ✗ ll                          
total 8
-rw-r--r--  1 wangxiyue  staff   411B Nov  9 22:58 SourceCodeExecDemo.java

可以看到确实没有 class 文件生成。

完全支持Linux容器

在Docker容器中运行Java应用程序一直存在一个问题,那就是在容器中运行的JVM程序在设置内存大小和CPU使用率后,会导致应用程序的性能下降。这是因为Java应用程序没有意识到它正在容器中运行。随着Java10的发布,这个问题总算得以解诀,JVM现在可以识别由容器控制组(cgroups) 设置的约束,可以在容器中使用内存和CPU约束来直接管理Java应用程序,其中包括:

  • 遵守容器中设置的内存限制
  • 在容器中设置可用的CPU
  • 在容器中设置CPU约束

支持Unicode 10

Unicode 10新增了8518个字符,总计达到了136690个字符。包括56个新的emoji表情符号。

JDK11在java.lang下增加了4个类来处理:

  • CharacterData00.class
  • CharacterData01.class
  • CharacterData02.class
  • CharacterData0E.class

新支持的加密算法

Java实现了RFC7539中指定的ChaCha20和Poly1305两种加密算法,代替RC4。 RFC7748定义的密钥协商方案更高效,更安全,JDK增加了两个新的接口XECPublicKey和XECPrivateKey。

垃圾回收器

ZGC

启用方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

说明:ZGC, A Scalable Low-Latency Garbage collector( Experimental) ,一个可伸缩的低延时的垃圾回收器。GC暂停时间不会超过10ms,既能处理几百兆的小堆,也能处理几个T的大堆。和G1相比,应用吞吐能力不会下降超过15%,为未来的GC功能和利用colord指针以及Load barriers 优化奠定了基础。初始只支持64位系统。

ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。将来还可以扩 展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。

GC是java主要优势之一。然而,当GC停顿太长,就会开始影响应用的响应时间。消除或者减少GC停顿时长,java将有可能在更广泛的应用场景中成长为一个更有吸引力的平台。此外,现代系统中可用内存不断增长,用户和程序员希望JVM能够以高效的方式充分利用这些内存,并且无需长时间的GC暂停时间。

ZGC是一个并发,基于region,压缩型的垃圾收集器,只有root扫描阶段会STW,因此GC停顿时间不会随着堆的增长和存活对象的增长而变长。

Epsilon

实验性质,生产环境不建议使用。

启用方法:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

说明:开发一个处理内存分配但不实现任何实际内存回收机制的GC,一旦可用堆内存用完,JVM就会退出,如果有System.gc()调用,实际上什么也不会发生(这种场景下和-XX:+DisableExplicitGC效果一样), 因为没有内存回收,这个实现可能会警告用户尝试强制GC是徒劳的。

主要用途如下:

  • 性能测试(它可以帮助过滤掉GC引起的性能假象)
  • 内存压力测试(例如,知道测试用例应该分配不超过1GB的内存,我们可以使用-Xmx1g -XX:+UseEpsilonGC,如果程序有问题,则程序会崩溃。
  • 非常短的JOB任务(对于这种任务,GC是在浪费资源)
  • VM接口测试
  • Last-drop延迟&吞吐改进

更好的G1

对于G1 GC,相比于JDK8,升级到JDK 11即可免费享受到:并行的Full GC,快速的CardTable扫描,自适应的堆占用比例调整(IHOP),在并发标记阶段的类型卸载等等。这些都是针对G1的不断增强,其中串行FullGC等甚至是曾经被广泛诟病的短板,你会发现GC配置和调优在JDK11中越来越方便。

移除与不再推荐使用的类库或功能

Jdk9到Jdk11,陆续移除了一些类库或功能。

移除了Java EE和CORBA Moudles

在java11中移除了不太使用的JavaEE模块和CORBA技术。

CORBA来自于二十世纪九十年代,Oracle认为,现在用CORBA开发现代Java应用程序已经没有意义了,维护CORBA的成本已经超过了保留它带来的好处。

但是删除CORBA将使得那些依赖于JDK提供部分CORBAAPI的CORBA实现无法运行。目前还没有第三方CORBA版本,也不确定是否会有第三方愿意接手CORBA API的维护工作。

在java11中将java9标记废弃的Java EE及CORBA模块移除掉,具体如下:

xml 相关被移除的:

  • java.xml.ws
  • java.xml.bind
  • java.xml.ws
  • java.xml.ws.annotation
  • jdk.xml.bind
  • jdk.xml.ws

只剩下java.xml, java.xml.crypto.jdk.xml.dom 这几个模块。

其它被移除的Java EE和CORBA相关类库:

  • java.corba
  • java.se.ee
  • java.activation
  • java.transaction(但是java11新增了一个java.transaction.xa模块)

其他移除的类库

  • com.sun.awt.AWTUtilities
  • sun.miss.Unsafe.defineClass
  • Thread.destroy() 以及 Thread.stop(Throwable) 方法
  • sun.nio.ch.disableSystemWideOverlappingFileLockCheck 属性
  • sun.locale.formatasdefault 属性
  • jdk snmp 模块
  • javafx
  • java Mission Control
  • Root Certificates: 一些根证书被移除:Baltimore Cybertrust Code Signing CA, SECOM Root Certificate, AOL and Swisscom Root Certificates

其中,使用java.lang.invoke.MethodHandles.Lookup.defineClass来替代移除的sun.miss.Unsafe.defineClass。

将Nashorn Javascript标记为不推荐

将Javascript引擎标记为Deprecate,后续版本会移除,有需要的可以考虑使用开源的GraalVM。

将Pack200 Tools and API标记为不推荐

java11中将pack200以及unpack200工具以及java.tiljar中的Pack200 API标记为Deprecate。因为Pack200主要是用来压缩jar包的工具,由于网络下载速度的提升以及java9引入模块化系统之后不再依赖Pack200,因此这个版本将其标记为Deprecate。

预告

继LTS JDK8之后,又一LTS,你会用吗? JDK16,它来了。

对了

JDK11 写 JDK8 的代码?

代码始终是代码,写的再多,写不懂你我。

多看一点,就比其他们多懂一点。所以,你关不关注我,问题不大!

人情世故。不是世故,就是事故。问题真的不大。

文中所有代码, 在 https://gitee.com/fangjiaxiaobai/learn_java/tree/master/fxb_jdk11

最后

希望和你一起遇见更好的自己

配套资料 · 免费领取
本文属于「JAVA」资料包范围:JDK 源码笔记合订版。HashMap、并发、Stream 等系列合订。
在公众号「方家小白」回复关键词 JAVA 即可领取。全文无需关注即可阅读。
JAVA