JDK 9 - 17 新特性 / 语法概览

6,035 阅读18分钟

JDK 8 版本的内容,见三篇笔记:

  1. Lambda 接口与方法引用:掘金 - 链接1
  2. Stream 流:掘金 - 链接2
  3. Arrays,Collections,Optional:掘金 - 链接3

其它零碎的知识:

  1. Java I/O 流:掘金 - 链接4
  2. Java 注解开发:掘金 - 链接5

JDK 17 是继 11 之后的又一个 TLS 版本,同时 Spring 官宣 Spring framework 6 和 Spring Boot 3 都将应用 JDK 17。事实上,已有不少 ( 也许被认为是 ) 过时的组件在 JDK 9 - 11 版本之间被删除,重构,或是迁移。因此在数年之后,各种 Java 框架的新发行版很可能不再迁就 "坚挺" 的 JDK 8,至少也应该是 JDK 11。比如在 2021 年 9 月份 Kafka 3.0.0 已经宣布不会继续提供对 JDK 8 的支持:

kafka-no8.png

用户在 Oracle 官网订阅的 JDK 17 在 2024 年 9 月之前都可以获得免费更新。出于避免潜在商业纠纷的考虑,用户也可以选择下载由其它厂商提供的 OpenJDK 并使用,见:Oracle JDK 收费后我们如何选择 ?

  1. Oracle OpenJDK :JDK 17.0.2 GA Release (java.net)
  2. Microsoft Build OpenJDK:下载 OpenJDK 的 Microsoft 内部版本 | Microsoft Docs ( 比较全 )
  3. 阿里巴巴 Alibaba Dragonwell ( Linux / alpine-Linux 版本 ) Dragonwell (dragonwell-jdk.io)

概括来看,JDK 17 的更新大部分是 JDK 9 - 16 版本各种预览特性 ( preview features ) 的汇总,同时完善了各类实用 API 库:比如 JDK 8 之后开始相继引入的异步编程接口 CompleteableFuture,HTTP 2 工具包 java.net.http ,安全库 java.scrutiny 等。与此同时,Java 的垃圾回收器也经历了不断地迭代和完善 ( 或者是淘汰 )。对于升级到新版本的 Java 用户而言,GC 优化方面可以完全算是 "免费且显著的性能提升"。

语法层面则几乎没有出现如 JDK 5 泛型,JDK 8 Lambda Expression 这样 "令人眼前一亮" 的重磅更新。显然,Java 更倾向于发展 "内力",毕竟一个内核强大稳定的 JDK 能够使所有 JVM 语言收益,包括 Kotlin,Groovy,Scala 等。

关于垃圾回收器

关于垃圾回收器的内容,见笔者《深入了解 JVM》 的笔记:JVM:垃圾收集器 - 掘金 (juejin.cn)

JDK 8 默认的垃圾回收器组合是:Parallel Scavenge ( 新生代 ) 和 Parallel ( 老年代 )。

在 JDK 9 之后,G1 成为默认的垃圾收集器。它将内存空间无差别地划分为 region 块,且将大型对象单独放在 Humongous 区域管理。其设计目标是建立 "可停顿时间模型",保证用户在 M 秒时间片内,回收器工作不超过 N ( GC 的资源用量占比不超过 N/M )。

CMS ( Concurrent Mark Sweep ) 在 Oracle JDK 14 当中被正式移除,同时弃用了 ParallelScavenge + serialOld GC 组合。

Oracle JDK 11 引入了新一代 ZGC 垃圾回收器,其支持 TB 级内存容量,暂停时间低 ( <10ms ),对整个程序吞吐量的影响小于 15%。在 JDK 15 之后,ZGC 可正式投入到生产环境中,并通过 -XX:+UseZGC 启动,且性能在 JDK 16,17 之后得到了进一步增强。ZGC 在各版本迭代中的优化历程可见:ZGC | What's new in JDK 17 (malloc.se)

Open JDK 推出的则是 Shenandoah 垃圾回收器,由 Red Hat 团队开发,是 ZGC 垃圾回收器的 "竞品"。但由于它并不是 Oracle 的 "亲儿子",因此在一定程度上遭到了排挤,Oracle 明确拒绝将这款垃圾回收器纳入其中。

Error occurred during initialization of VM
Option -XX:+UseShenandoahGC not supported

其性能介绍见:Shenandoah in OpenJDK 17: Sub-millisecond GC pauses | Red Hat Developer

JDK 9 之后的版本至今,默认的垃圾回收器仍然为 G1。

JDK 9 (2017.9)

JDK 9 还引入了重要内容:即模块化,它是一个比较庞大的概念,被单独列为二级标题,见文末。

jshell ( Real-Eval-Print Loop )

REPL 工具在现代的编程语言已经成为标配,它于 JDK 9 版本中被引入。尤其是仅需要简单测试某些语句和表达式时,用户不需要另启项目了:

PS C:\Users\liJunhu\IdeaProjects\testForJava17> jshell
|  Welcome to JShell -- Version 17.0.2
|  For an introduction type: /help intro

jshell> int i = 10
i ==> 10

jshell> System.out.println(i)
10

接口增强

JDK 8,9 两个版本中均对接口的概念做了少许拓展,现在接口支持定义默认方法,静态方法/属性,私有方法。

interface MathUtil{
    // implements 该接口的类将自动获取该方法。 - jdk 8.
    default void h(){}

    // 在接口定义的属性直接被视为 static。 -jdk 8.
    double Pi = 3.1415;

    // 可以在接口直接定义静态方法。 - jdk 8.
    static void f(){}
    
    // 可以在接口内直接定义私有方法。 -jdk 9.
    private void g(){}
}

String 底层结构变更

在 JDK 8 及之前的版本当中,String 的底层使用 char[] 数组存储内容;而在 JDK 9 之后,字符串的内容实际改用 bytes[] 字节形式存储,并配合 encodingFlag 配合编码解码。这个修改对上层代码透明,即用户层面的各种 String 操作没有发生任何变化。

API:of 创建不可变序列

Map,List,Set 等现在提供 of 方法创建 不可变 的集合实例,其中 Map 支持两种创建方式:

List<Integer> list = List.of(1, 2, 3, 4, 5);
Set<Integer> set = Set.of(1, 2, 3, 4, 5);

// k1,v1,k2,v2,...,形式创建
Map<Integer, String> map1 = Map.of(1, "v1", 2, "v2");

// 通过 Map.entry 形式创建
Map<Integer, String> map2 = Map.ofEntries(
    Map.entry(1, "v1"),
    Map.entry(2, "v2"),
    Map.entry(3, "v3"),
    Map.entry(4, "v4")
);

API:HTTP 2 协议接口

JDK 9 中引入了新的 API 以支持 HTTP 2.0 版本。注,部分 API 在 JDK 11 之后的版本发生了更替。

// throws IOException, InterruptedException
// 构建 client, 支持 http 2.0
HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();

// 构建请求
HttpRequest req = HttpRequest.newBuilder(URI.create("https://www.baidu.com")).build();

// 发送请求
HttpResponse<String> resp = client.send(req,HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());

JDK 10 (2018.3)

引入 var 关键字

JDK 10 支持使用 var 简化局部变化的类型声明,比如:

// 显然 arrayList 是一个 ArrayList<Integer> 类型。
ArrayList<Integer> arrayList = new ArrayList<>();

适当的精简可以提高代码的可读性,其 arrayList 的实际类型将交给编译器进行推断。

var arrayList = new ArrayList<Integer>();

JDK 11 (2018.9,TLS*)

API:字符串增强

JDK 11 新增了部分对字符串的处理方法。

println("".isBlank());      // 判断字符串是否为 "".
println("  welcome to java 17 ".strip());  // 去掉首尾空格
println("  welcome to java 17".stripLeading()); // 去掉首部空格
println("welcome to java 17 ".stripTrailing()); // 去掉尾部空格
println("word".repeat(2));     // "wordword"

lambda 表达式类型推导

JDK 11 允许将 var 关键字应用到 lambda 表达式中,起到简化声明的作用。比如:

@FunctionalInterface
interface Mapper<A,B>{
       B map(A a);
}

// var 关键字现可以用于 lambda 表达式。
Mapper<String,Integer> string2int = (var a) -> a.length(); 

JDK 13 (2019.9)

增强 switch (jdk13, 14)

增强版 switch 在 JDK 12 作为预览特性引入。在 JDK 14 之后,增强版 switch 语句块具备返回值。

var simple = switch (lang) {
    case "java" -> "j";
    case "go"   -> "g";
    default -> "<non>";
};

在新版本的 switch 语句中,分支创建可以使用 -> 符号,且 cases 的判断不会引发穿透现象,因此不需要显式地在每一个分支后面标注 break 了。下面例子展示了增强 switch 的更多特性:

var simple = switch (lang) {
    // 1. 允许在一个 case 中匹配多个值    
    case "java","scala","groovy" -> "jvm";
    case "c","cpp" -> "c";
    case "go"   -> "g";
    default -> {
        // 2. 复杂分支内返回值使用 yield 关键字 (相当于 return)
        if(lang == null){
            yield "<unknown>";
        }else yield "<non>";
    }
};

文本块 TextBlock

JDK 13 允许使用三引号 """ 表示长文本内容。

var block = """
        lang: java
        version: 13
        dbname: mysql
        ip: 192.168.140.2
        usr: root
        pwd: 1000
        """;

// 6 行
block.lines().count();

文本块内允许插入 \ 阻止换行,如:

var block = """
        lang: java\
        version: 13\
        dbname: mysql\
        ip: 192.168.140.2\
        usr: root\
        pwd: 1000
        """;
// 实际效果:            
// "lang:javaversion: 13dbname: mysql..."
// 1 行
block.lines().count();

另外注意,每行末尾的空格会被忽略,除非主动将其替换为 /s

JDK 16 (2021.3)

instanceof 模式匹配

如果一个上转型对象的具体类型已经被 instanceof 关键字确定,那么其后续操作可省略强制转换,见下面的示例:

// o 可能是 String 类型或者是 Double 类型。
Object o = new Random().nextInt() % 2 == 0 ?  "java16" : 1000.0d;

// s 相当于被确定类型之后的 o。
if(o instanceof String s){
    // 不再需要 ((String)o).length()
    System.out.println(s.length());
}else {
    System.out.println();
}

Records

record 关键字修饰的类相当于 Scala 的样例类 case class,或者可看成是 Lombok 中 @Data 注解的一个 "低配" 替代。编译器会一次性代替用户生成 gettersconstructorstoString 等方法。

record Student(String name,Integer age){}
// ....
var student = new Student("Wang Fang",13);
student.age();
student.name();

JDK 17 (2021.9,TLS*)

密封类

其 Sealed class 的概念和 Scala 类似,密封类不允许在包外被继承拓展,密封类必须具备子类。

// 密封类
public sealed class People {}

// 密封类 People 必须至少有一个子类。
// 非密封的 People 子类
non-sealed class Teacher extends People{}

// 密封的 People 子类。
sealed class Driver extends People{}
non-sealed class TruckDriver extends Driver{}

密封类可以声明子类,但必须需要严格声明其子类是密封 ( sealed ) 或者是非密封 ( non-sealed ) 的。以下是 JDK 17 的预览功能permits 关键字可进一步限制同一个包下有哪些类允许继承它。

// 即使在一个包下,也只有 Teacher 和 Driver 被允许继承 People。
public sealed class People permits Teacher,Driver{}

non-sealed class Teacher extends People{}
non-sealed class Driver extends People{}

增强 switch (jdk 17)

JDK 17 对 switch 语句做了进一步增强,它现在支持匹配类型以及判空的功能:

Number v1 = 3.1415d;

switch (v1) {
    case null -> println("null");
    case Float f -> println(f);
    case Double d -> println(d);
    default -> println("NaN");
}

注,该语法目前是 JDK 17 的预览功能

JDK 9 模块化

JDK 9 引入了模块化特性,称 Java Platform Module System,简称 JPMS,或者 Project jigsaw。模块化的优势有三点:

  1. 改进组件之间的依赖管理,引入比 jar 包粒度更大的 Module。
  2. 使得 java 程序更容易轻量级部署。
  3. 改进性能和安全性。

模块化还使得精简庞大的 JRE 成为可能,见后文的 JLink。

Why we need module

(18条消息) JDK9-JPMS模块化wshten-CSDN博客jpms

在提及模块化之前,首先要谈谈 "为什么不能只依赖 jar 包"。直到 JDK 8 之前,一切 Java 工程都是基于 jar 包构建的,而 jar 包本质上只是 "多个 package 的压缩包",自身完全不携带任何描述控制权限和引用依赖等信息。

因此,jar 包本身不会告知 JVM 它还依赖哪些 jar 包,这完全需要开发者自行判断 jar 包之间的依赖关系并下载缺失的 jar 包。好在后来用户拥有了 Maven,Gradle,Sbt 这类包管理工具,才避免了 Jar-hell 的问题 ( 类似地,还有 Windows 的 dll-hell )。

模块 module 本质上仍然是 jar 包 ( 下文称作为模块的 jar 包为 module-jar ),但它额外增加了一个描述模块规则的 module-info.java 文件,可以理解成 "模块 = 普通 jar 包 + 权限控制"。jar 包之间的关联现在就是靠 module-info.java 文件来组织的,其格式为:

// ${moduleName} 填写模块名称。
module ${moduleName} {
    // 描述此 module 的更多规则,见后文。
}

原则上模块的命名应当和项目名保持一致,但这并不是必须的。注,模块 module 之间是平级的,只有相互依赖的关系,没有 has-a 的 "父子关系"。

"导入 jar 还是 module-jar " 对 用户透明,用户可以放心地将模块的 jar 包当成普通 jar 包来使用,也可以将普通 jar 包当成 module-jar 包来使用 ( 原因见后文的模块分类部分 )。在 IntelliJ IDEA 编译器下打包一个 module-jar, 除了 在项目根目录下 新建一个 module-info.java 文件之外,其它步骤和打包一个普通 jar 没有什么区别。

module-jar.png

( 注:From modules with dependencies... 选项可让 IDE 补充 Jar 包所需的 META-INF/MANIFEST.MF )

特别注意,用户如果打算打包一个 module-jar,那么类文件将不能声明在项目的顶级目录,通俗的说就是不能把类文件直接扔在 src 源文件根目录下了。否则会报错:

Caused by: java.lang.module.InvalidModuleDescriptorException: XXX.class found in top-level directory (unnamed package not allowed in module)

JDK 9 之后有两种执行模式:作为普通的 jar 包执行 ( 向后兼容 ),或者是作为 module-jar 执行。

# 视作普通的 jar 包运行
java -jar ${JarPath}
# 运行 module-jar 包,
# --module-path, -p : 提供目标模块及依赖的 .jar, .jmods 所在的目录。 
# --module, -m : 提供模块名称以及主程序类全限定名。
java --module-path ${ModulePath} --module ${moduleName}/${MainClass}
# 示例
# java --module-path . --module priv.jdk9test.utils/priv.jdk9test.utils.TimePrinter

--module-path--class-path 的概念很相似。不过 --module-path 中的 *.jar 或者是 *.jmod 文件 ( 该文件格式见后文的 JMOD ) 被当作模块来处理,而 --class-path 中的的 jar 包仍按照传统的方式处理。

java9 classpath_Java9 中的 Module, ModulePath 和 ClassPath_er0s10n的博客-CSDN博客

细则

下面简单介绍 module-info.java 文件的几个权限控制规则:

  1. exports [ to ... ]
  2. opens [ to ... ]
  3. requires [ static | transitive ]
  4. provide ... with ...
  5. uses ...

注意,模块导出 ( exports ) 以 ( package ) 为单位,而导入 ( requires ) 以模块为单位,只有声明导入了模块,才能继续在代码中 import 此模块 导出的包

模块 ( 包括后文的 ) 约束 只对模块起作用。如果用户的项目只是普通项目 ( 没有编写 module-info.java 文件 ),那么反而不会受限制,这听起来虽然有点奇怪,但这种考量是向后兼容的权衡之举,主要是为了照顾那些没有引入 JDK 9 模块化的旧工程。

最基本的关键字是 exposes ,它表明模块将导出哪些包 ( package ) 供其它模块引用。

module priv.jdk9test.helper {
    exposes priv.jdk9test.helper;
}

越界 import 模块内容会被编译器拦截。大意为:在 xxx 模块下定义的 ppp 包并没有导出给 yyy

Package 'ppp' is declared in module 'xxx', which does not export it to module 'yyy'

同时,越界反射模块内容也会在运行期被拦截,并抛出 java.lang.IllegalAccessException。若想要限制某些包在运行期可被反射获取,但在编译期不可用,则可以用 opens 替代 exports

// 编译期禁止出现对 priv.jdk9test.unsafe 的 import。
// 但是允许在运行期通过反射获取。
opens priv.jdk9test.unsafe

可以指定将包开放给指定的模块,使用 exports ... to ... 语法,比如:

module priv.jdk9test.helper{
    exports priv.jkd9test.helper;
    // 导出给多个模块可使用 , 分隔。
    exports priv.jdk9test.services to priv.jdk9test.user;
}

而依赖方需要通过 requires 将其它模块纳入声明中,比如:

module priv.jdk9test.user {
    requires priv.jdk9test.helper;
    requires priv.jdk9test.service;
}

导入声明首先会强制要求指定的 module-jar 已经被加载进依赖路径中,否则编译会不通过,这避免了运行期出现 ClassNotFound 的问题。另一方面,只有主动声明导入 ( requires ) 之后才可以继续在本模块内 import 其 module-jar exports 给自身的内容。需要注意,如果模块 C requires 的模块 A 事实上并没有向模块 C exports 任何包,那么这条 requires 声明不会报错,但实际上不会起任何作用

顺带一提,如果用户正在 IntelliJ IDEA 环境下开发多个子项目,且子项目之间相互引用 ( 比如 priv.jdk9test.user 需要使用 priv.jdk9test.helper 编译出的 jar 包 ),可以在 project structures 进行如下设置 ( 重点是黄字部分 ):

modules-select.png

requires 基础之上还附带 transitive 传递规则。它的作用是:若模块 B requires transitive 另一个模块 A,现有另一模块 C requires 模块 B,则它相当于隐式地声明了 requires A。

// Module A -> module-info.java
module A {
    exports a1 to B;
    exports a2 to C;
}
​
// Module B -> module-info.java
module B {
    // 传递导入模块 A
    requires transitive A;    
    // 模块 B 声明导出的包,其中 b2 仅向 C 导出。
    exports b1;
    exports b2 to C;
}
​
// Module C -> module-info.java
module C {
    requires B;
    // 由于 B 传递导入了模块 A,因此
    // 模块 C 相当于隐式地声明了:
    // requires A;
}

在上面的例子当中,即使在模块 C 的规则文件中不主动声明 requires A,它也能够直接访问模块 A 开放的 a2 包。如果模块 B 对模块 A 的导入并不是传递性质的,那么模块 C 就必须主动附加上这条声明。

如果某些依赖模块只在编译时需要,那么可以为其添加 static 关键字:

requires static priv.jdk9test.initializer

模块化中另一个特殊的导入导出是 usesprovides with,它们类似于 Java 的服务发现机制 SPI,实现了接口和实现类的解耦。服务的提供方需要开放自己的接口,并使用 provides ... with ... 声明此服务接口的具体实现类。

// 开放服务接口
exports priv.jdk9test.services to priv.jdk9test.user;
​
// 提供服务接口的实现
provides priv.jdk9test.services.MyService with priv.jdk9test.servicesImpl.IPService;

客户端则需要在 module-info.java 文件中声明对接口的使用 uses ,其具体的实现由 ServiceLoader 负责加载。

// module-info.java
// uses priv.jdk9test.services.MyService// 通过 ServiceLoader 获取服务接口的实现。
// 这段代码没有声明任何关于 priv.jdk9test.servicesImpl.* 的任何声明。
// 返回的服务实现用 Provider<T> 包装,因此还需要借助 map 提取出来。
List<MyService> services = ServiceLoader.load(
    MyService.class
).stream().map(
    ServiceLoader.Provider::get
).toList();
​
// 实际上调用的是 priv.jdk9test.servicesImpl
services.forEach(MyService::getService);

在 JDK 9 当中,JDK 被分为了 94 个 modules,现在只需加载用户程序依赖的 modules 。Java 保留了一个重要的基础模块 java.base,它仅对外导出而没有任何导入:

module java.base {
    exports java.io;
    exports java.lang;
    exports java.module;
    exports java.math;
    exports java.nio;
    exports java.util;
    ...
}

所有的模块默认依赖 java.base 模块。

模块分类

JDK 9 中的模块化实际上分为四种:普通模块,开放模块,自动模块,未命名模块。其中,普通模块遵循上述的细则进行权限控制。

开放模块的声明格式如下:

open module ${module-name} {
    // 不适用 opens 规则。
}

开放模块意味着内部所有的声明都是默认可以在运行期被反射获取的。和普通模块相比,开放模块可以声明 exportsrequiresprovides & uses,但是不包括 opens

当一个普通 jar 包 ( 通常都是在 JDK 8 及之前编译的 ) 被放在模块搜索路径 --module-path 时,它将被视作一个自动模块,这个模块的名称和版本由 jar 包文件的名称派生。此举是 JDK 9 为向后兼容而设计的机制,自动模块总是读取所有模块,且打开 ( open ) 并导出 ( exports ) 所有包,这也解释了在前文删除 module-info.java 文件之后,所有的模块约束反而都 "消失" 的原因。

由于并不是所有的依赖库的厂商目前都提供了模块化版本,因此,若要将旧项目迁移到 JDK 9 及更新的版本,可以将各种依赖放到 --module-path 下,将它们变为自动模块。

可以将 jar 或者 module-jar 放在类搜索路径 --class-path。这样,当类型加载器在 --module-path 找不到类文件时,模块系统便尝试在类路径中寻找匹配的类型。如果成功了,则此类型会被归到 unnamed 模块 ( 未命名模块 ) 下。注意,其它模块无法对这个模块声明 requires

JLink

使用 JLink 可以生成一个 Java 程序的运行时镜像,它仅包含精简版 JRE + 项目代码。这意味着:

  1. 节省了内存,提高了性能。
  2. 允许开发仅提供很小内存的微服务。
  3. 更加适合物联网设备。

以下是几个重要的可选参数:

# 模块所在的路径,自动包含 jdk jmods.
--module-path <path>
-p <path>
​
# 添加的模块,至少要有一个。多个模块可以用 , 分割
# 模块的来源可以是 *.jar,也可以是 *.jmod。
# jlink 在 --module-path 指定的路径下搜索模块。
--add-modules <mod>[,<mod>...]
​
# 输出文件夹
--output <path>

下面的 jlink 中,在当前目录 . 下寻找模块,并向 ./jdk9Project 输出两个模块。

 jlink --module-path . --add-modules priv.jdk9test.utils,priv.jdk9test.helper --output ./jdk9Project

所有的用户程序及其依赖被压缩到 lib/moudles 内。进入到输出路径的 /bin 目录下即可通过 java 命令执行这个镜像:

cd bin
# -p : 依赖模块的路径
# -m : 作为程序入口的模块
java -m testForJava/priv.jdk9test.utils.TimePrinter

IntelliJ IDEA 在 Project Structure 中提供了打包 Jlink 的选项。

idea_jlink.png

JMOD

JMOD 文件被设计成可以打包比 jar 更丰富的内容,包括本地代码,配置文件,本地命令和其它类型的数据,所以 JMOD 适合于那些依赖本地环境的模块,比如在 Windows 版本的 JDK 中 java.base.jmods/lib 携带 *.dll 文件,而在 Linux 版本下则是 *.so 文件 ( 因此 JMOD 本身不一定是跨平台的 )。

JDK 9 版本之后,jdk 目录下原有的 jre 目录被移除,取而代之的是 jmods 目录,内部保存着 jdk 被模块化拆分后的各种 *.jmod 文件,以供 JLink 工具提取并构建最小化的运行时镜像。JMOD 文件通过 JDK 提供的 jmod 工具打包,它位于 %JAVA_HOME%\bin 目录下。

打包 JMOD 之前需要将文件归类后存放到不同的路径下,由 jmod 工具转储到 *.jmod 包下的不同目录中去 ( 目录的命名遵从 jmod 自身定义的规范,如配置文件保存在 conf 目录下 )。

# 编译的 class 文件路径
--class-path <path>
# 本地命令路径
--cmds <path>
# 用户可编辑的配置文件路径
--config <path>
# 本地链接库路径,.dll,.so 等。
--libs <path>
# 头文件路径
--header-files <path>
# module 路径
--module-path <path>
-p <path>

下面的命令演示了如何将各种文件打包为一个 jmod。其中 --class-path 是必须的:

jmod create --config configs/ --class-path classes/* aJmod.jmod

Maven 提供 jmod 插件,见:Apache Maven JMod Plugin – jmod:create

关于 JMOD 的文件格式还是一个开放的 issuse,目前它是基于 .zip 格式的,因此只需简单更改后缀名即可查看 *.jmod 的内容。JMOD 仅限在编译或者在链接时使用,比如在 JLink 工具中可以直接将其内部的模块添加到 --add-modules 当中,相关的配置,头文件等也会自动迁移到镜像的对应目录。

Maven 项目的 Jlink 插件,见:Apache Maven JLink Plugin – jlink:jlink

参考资料

JDK12新特性详解 - 纯粹而又极致的光--木九天 - OSCHINA - 中文开源技术交流社区

Oracle JDK17及以后的版本真的都免费么? - 知乎 (zhihu.com)

OpenJDK和OracleJDK的区别 - 程序员自由之路 - 博客园 (cnblogs.com)

JDK9 新特性 - 念念就忘 - 博客园 (cnblogs.com)

Java Platform, Standard Edition What’s New in Oracle JDK 9, Release 9

java9到17版本中的新特性 - 知乎 (zhihu.com)

java9新特性(简述十大新特性) (360doc.com)

java jmod 学习 - 知乎 (zhihu.com)

发布你的 JAVA 程序——使用 JLINK - 知乎 (zhihu.com)

jmod 是 java 的文件嘛java11教程--jmod命令小小羊羊羊的博客-CSDN博客

idea中jre system library_Java 中的Jlink详解_早鸟的博客-CSDN博客

java9 揭秘 jlink使用jlink打包的java应用草莓味儿柠檬的博客-CSDN博客

JDK9-模块化系统 - 小路不懂2 - 博客园 (cnblogs.com)

JDK9的新特性:JPMS模块化 - 知乎 (zhihu.com)

java 9 功能的新特性 (一)模块化 - 简书 (jianshu.com)