为什么 Java9引入了 JMOD 文件格式?

Java9有三种方法将已编译的代码打包到文件中:

  • JAR
  • JMOD
  • JIMAGE

JIMAGE 针对速度和空间进行了优化,并由 JVM 在运行时使用,因此引入 JIMAGE 是有意义的。JIMAGE 文件不应该被发布到 maven 回购协议或在编译或链接时使用。

文档声称 JMOD 可以存储本地代码和其他 JAR 文件无法存储的内容,开发人员可以创建并分发自己的 JMOD 文件。JDK 附带了 jmods/目录,其中包含用户可以依赖的 JDK 的所有模块。

问题:

  • 为什么 Java9引入了 JMOD 文件格式?
  • 库作者应该分发一个 JMOD 文件还是一个 JAR 文件,或者两者兼而有之?
  • Jmod 文件应该发布到 maven 回购协议吗?
19496 次浏览

下面是从 JEP 261: 模块系统引用的一些内容,其中包含一个关于 JMOD 文件的部分。

为什么?

来自 JEP 261:

新的 JMOD 格式超越了 JAR 文件,包含了本地代码, 配置文件,以及其他类型的数据不适合 自然地(如果有的话)转换为 JAR 文件。

还有

JMOD 文件的最终格式是一个尚未解决的问题,但目前是 基于 ZIP 文件。

开发人员应该发布 JMOD 文件吗?

请注意,JMOD 文件似乎是在编译时和链接时合并本机代码的一种方法(还有其他方法)。来自 JEP 261:

JMOD 文件可以在编译时和链接时使用,但不能在运行时使用 时间。

(老实说,我不确定在 JDK9之前本机代码是如何发布的。)对于绝大多数开发人员(没有本地库或其他角落案例) ,我们只会发布模块化的 jar。

JMOD 的用途没有很好的文档,现有的文档相当稀少。这里是一个系统的深入解释,从我的理解。

警告 : 这个答案的一部分相当长,冗长,部分多余,而且很难阅读。建设性的、结构性的或者语法性的编辑是非常受欢迎的,可以提高未来读者的可读性。


简短回答

Java 9的新模块系统 竖锯计划引入了新的可选 链接时间阶段的概念,当使用 CLI 工具 jlink构建定制的空间优化的 JRE 时,就会出现这种情况。jlink将所有显式/传递 JAR 模块/JMOD 依赖关系打包到一个缩小的 JRE 中; 依赖关系图中所有其他不可到达的依赖关系(从指定的根模块开始)都打包到构建的 JRE 中。从 JDK9 + 开始,Java 的所有标准库都被分解为位于 <jdk>/jmods的 JMOD。

JAR 只能包含 .class和资源文件,而 JMOD (即 .jmod文件)包含额外的文件,这些文件是在新的可选 链接时间阶段中专门用来定制 JRE 的(例如,可执行文件、本机库、配置、合法许可证等)。这些附加文件在类路径的运行时不能作为资源使用,而是安装在构建的 JRE 中的不同位置(例如,可执行文件和本机库放在 <jre>/bin下)。从相关的捆绑 JAR 和 JMOD 依赖关系中,类和文件资源将被写入一个优化的 JIMAGE 文件中,该文件位于 <jre>/lib/modules(在 Java8和以前的版本中替换 <jre>/lib/rt.jar)。JMOD 的作用是在编译时和链接时,并且是 没有设计用于在运行时使用的。

对于一般的库/应用程序,应该只构建和推送 JAR,而不是 JMOD; 只有在某些条件下,JMOD 才能提供 链接时间阶段所需的关键功能。在撰写本文时,Maven 似乎并没有提供对 alpha 版本插件 org.apache.maven.plugins:maven-jmod-plugin以外的 JMOD 的强大支持。


长答案

这个冗长的答案是更复杂的动机,并揭示了一些新的模块系统从根本上如何运作的光。整篇文章都强调了 CLI 工具 jlink,因为 JMOD 是专门为该工具引入的这个新的可选 链接时间阶段设计的。

拼图工程简介

Java9在“ JEP 261: 模块系统”中引入了 竖锯计划,这是一个新的模块系统,可以用来最小化启动时间和 JRE 的大小。作为本版本的一部分,引入了 CLI 实用程序 jmodjimagejlink以及用于 JMOD/.jmod(基于 ZIP)和 JIMAGEs/.jimage的新文件格式。

这个新模块系统的一个重要特点是,CLI 工具 jlink使开发人员能够构建一个定制的 JRE,该 JRE 只包含与其应用程序相关的标准库和外部依赖项。这引入了 compile time -> run time流水线中传统相之间可选 链接时间相的新概念。

举一个使用 jlink的优点的例子,一个由 JDK 15构建的只有 java.base模块的极简主义 JRE 大小约为40MB,与 JDK 15的310MB 大小完全并列。这对于运送最小的自定义 JRE (如精益 Docker 映像)特别有用。新的模块系统为 Java 生态系统带来了巨大的好处,这些好处已经在其他地方进行了详细讨论,因此本文没有进一步详细阐述。

3J: 罐子,JMOD 和 JIMAGEs

JAR、 JMOD 和 JIMAGEs 的高级描述并不能很快解释这三种文件格式之间的明显区别。以下是对每个目标的一个非详尽的概述:

  • JAR: 基于 ZIP 文件格式的经典格式,用于将类和资源打包到 运行时间的类路径中。这是自1997年 JDK 1.1以来提出的事实上的主流标准。可以使用 java -cp/-classpath标志将 JAR 添加到类路径。几乎每个库或依赖关系 已经威尔都使用这种格式,因此在本节中对它进行了忽略。

  • JMOD: 一种基于 ZIP 文件格式的新格式,用于捆绑 JAR 可以包含的相同内容,但支持在构建自定义 JRE 的可选 链接时间阶段使用的其他文件(例如可执行文件、本机库、配置、合法许可证等)。JMOD 设计为在编译时和链接时使用,而在运行时使用 没有。引入这种新格式(而不是扩展 JAR)可能是因为这种新的基于归档的格式中的目录有特殊的意义,即 没有向后兼容已经使用相同目录名的 JAR。

    • 可以使用 CLI 工具 jmod从 JAR 模块(即包含有效的 module-info.class)构造 JMOD。
    • 从 JDK9开始,所有 Java 标准模块都存储在 JDK 安装的 <jdk>/jmods下。
    • JMOD 可以发布供其他开发人员和上游应用程序使用; 在撰写本文时,我不确定是否可以将 JMOD 推送到 Maven 存储库,但各种来源似乎表明目前还不太可能。
    • 在带有 java -cp/-classpath标志的类路径的 运行时间中使用 JMOD 类和资源 不能,因为 JMOD 归档中的类和资源存储在 classes下,而不是在归档根目录中。

注意: 可能有一种方法可以很容易地将 JMOD 添加到 运行时间的类路径中; 然而,研究并没有明确说明与此相关的任何功能。仅仅向类路径添加一个 JMOD 将不足以使用类和资源。但是,可以使用定制的 ClassLoader在运行时正确地解析 JMOD 归档文件中的类和资源文件; 通常不推荐这样做,JMOD 也不需要这样做。

  • JIMAGEs : 在‘ JEP 220: 模块化运行时图像’中引入的一种特殊文件格式,它是一个包含 JRE (即标准库)所需的所有类和资源的 运行时间映像。在 JRE/JDK 9之前,使用了一个位于 <jre>/lib/rt.jar的单个大型非模块化超级 JAR; 后来,它被移除,而使用存储在 <jre>/lib/modules的单个优化的 JIMAGE。这种格式是基于 ZIP 格式的 没有,使用的自定义格式比原始的遗留 JAR 格式具有更高的时间和空间效率,从而减少了启动时间。
    • 当使用 CLI 工具 jlink构建定制的 JRE 映像时,所有相关(显式的或传递的)模块依赖的类和资源(来自 JAR 模块或 JMOD)都被编译成单个优化的 JIMAGE 文件(同样,存储在 <jre>/lib/modules下)。
    • JIMAGE 文件格式是模块化的,可以使用 CLI 工具 jimage创建、修改、拆卸或检查。例如 jimage list $JAVA_HOME/lib/modules
    • JIMAGEs 通常不应该发布,而应该附带一个特定的自定义 JRE 版本; 文件格式可能会在将来发生变化。

实质: JMOD 的详细目的

一个新的,可选的 链接时间阶段

如前所述,CLI 工具 jlink在普通 Java 管道中引入了一个新的可选阶段—— 链接时间相位 。此链接时间阶段用于从一组 Java9模块(带有 module-info.java描述符的 JAR 或 JMOD)生成自定义构建的 JRE。

高级别阶段简述如下:

  • 编译时间 (javac) : 如 javac文档中所述,编译时间阶段..。

    ... 读取用 Java 编程语言编写的类和接口定义,并将它们编译成字节码类文件。它还可以处理 Java 源文件和类中的注释。

  • 链路时间 (jlink) : 如‘ JEP 282: jlink: Java 链接器’中所述,链路时间阶段为..。

    ... 在编译阶段(javac 命令)和运行阶段(java 运行时启动程序)之间的一个可选阶段。链接时需要一个链接工具,该工具将组装和优化一组模块及其传递依赖关系,以创建运行时映像或可执行文件。

    链接时间是进行全局优化的一个机会,而这些优化在编译时是困难的,在运行时则是代价高昂的。一个例子就是当计算的所有输入都变为常数时(也就是说,不是未知的)优化计算。后续的优化将是删除不再可达的代码。

  • 运行时 (java) : 如 javac文档中所述,运行时阶段..。

    启动一个 Java 应用程序。它通过启动 JRE (JRE)、加载指定的类并调用该类的 main ()方法来实现这一点。

JMOD 介绍

在链接阶段,来自模块(有效的 JAR 模块或表单 JMOD 的 classes)的所有类和资源都被编译成位于 <jre>/lib/modules的单个优化的 JIMAGE 运行时映像。没有显式或过渡包含的模块将 没有包含在最终的 JIMAGE 中,从而节省了大量的空间。但是,在构建自定义 JRE 时,JRE 内部可能需要一些额外的文件; 例如可执行命令或本机库。对于 JAR 模块,故事到此结束—— JAR 无法毫不含糊地将文件(JIMAGE 中包含的类以外的文件)添加到构建的 JRE 中。

介绍 JMOD: JMOD 能够将其他文件添加到自定义构建的 JRE 中; 一些示例(但不一定详尽) : 可执行命令、配置文件、头文件、法律通知和许可证、本机库和手册页。这允许模块依赖性以其自己的方式形成构建的 JRE。CLI 工具 jlink如何将这些附加文件插入到构建的 JRE 中的行为将在下一节中进行说明。

JMOD 的目的地是 独自一人的编译时间和链接时间阶段,如‘ JEP 261: 模块系统’中所述:

JMOD 文件可以在编译时和链接时使用,但不能在运行时使用。为了在运行时支持它们,通常需要准备动态提取和链接本机代码库。这在大多数平台上都是可行的,尽管它可能非常棘手,而且我们没有看到许多需要这种能力的用例,所以为了简单起见,我们选择在这个版本中限制 JMOD 文件的实用性。

新格式-不与 JAR 向后兼容

一个很好的问题可能是“为什么不启用 JAR 来添加链接时行为?”.这里有一个潜在的怀疑是,这不能为现有的 JAR 和工具提供足够的向后兼容性支持。在 JAR 归档文件中没有保留文件名的规范。如果现有库将任何资源存储在用于链接时的目录下,则 jlink无法准确地猜测是在链接时使用还是在运行时需要使用。带有保留目录名的新文件格式规范可以解决这个冲突问题——比如新的 JMOD 格式。使用 JMOD,对于为链接时和运行时指定哪些资源没有任何歧义。此外,还可以扩展 JMOD 格式,以便在以后的 JDK 版本中添加新功能,而不存在向后兼容性问题。

JMOD 文件格式类似于 JAR,因为它基于 ZIP 文件格式。JMOD 文件具有以下保留的目录名,具有以下行为(这不一定是详尽无遗的列表!):

  • bin(--cmds) : 复制到 <jre>/bin的可执行命令
  • classes(--class-path) : 用于包含到最终构建的 JIMAGE 中,存储在 <jre>/lib/modules
  • conf(--config) : 复制到 <jre>/conf的附加配置; 如果需要,可能用于控制任何绑定模块的配置
  • include(--header-files) : 将额外的 C 头文件复制到 <jre>/include/,用于使用 JNI 为 JVM 构建 C 库; 例如,在 java.base中,导出 JNI 接口
  • legal(--legal-notices) : 复制到 <jre>/legal/<module name>/的模块的法律通知和许可证
  • lib(--libs) : 复制到 <jre>/bin的本机库

对于奇怪的倾向,标准库 JMOD (位于 JDK9 + 中的 $JAVA_HOME/jmods之下)可以用任何读取 ZIP 归档的应用程序进行检查。

主流支持?

JMOD 没有被迅速采用和文档可用性差的一个重要原因是,简单地说,它们对于绝大多数库和模块依赖关系来说是不必要的。虽然它们对于特定的用例仍然有用,但是模块应该使用自从1997年用 JDK 1.1定义以来已经得到主流支持的 JAR 格式(在2017年用 JDK 9添加了 module-info.java模块支持)。

根据 CLI 工具 jmod的文档:

对于大多数开发任务,包括在模块路径上部署模块或将它们发布到 Maven 存储库,将继续将模块打包到模块化 JAR 文件中。Jmod 工具用于具有本机库或其他配置文件的模块,或者用于使用 jlink 工具链接到运行时映像的模块。

一个观点 : 至少在 非常的很长一段时间内,JMOD 很可能不会被开发人员大量采用。大多数开发人员将永远不会听到或知道 JMOD 的用途——他们也不需要知道。JMOD 在构建 JRE (所有的 Java 标准库模块都是 JMOD)的背后起着至关重要的作用,但是由于它们在链接时的特殊用例,并不影响绝大多数的应用程序和项目。Java 9于2017年发布,Java 生态系统中的依赖关系仍然很难可靠地使用 module-info.class描述符使 JAR 成为一个有效的完全成熟的模块..。

外卖

  • JMOD 是使用 CLI 工具 jlink创建 JRE 的一个基本的新特性,jlink支持使用附加文件定制自定义构建的 JRE。
  • 部署 JAR 而不是 JMOD,除非特别需要 JMOD 的某些特性。JAR 模块也与 jlink兼容,所以不需要提供只包含类和资源的 JMOD。生态系统支持和工具不一定会很快采用 JMOD,而且在未来几年肯定会有兼容性问题。
  • 针对生态系统的这个领域的 Java 文档可以使用一些改进。

免责声明

在编写这个答案的时候,关于 Java9及以后的 JMOD 的用途的文档很少。事实上,Google 搜索短语“ java jmods”和“ jmod format”在第二个搜索结果中带来了同样的 StackOverflow 问题。因此,有些方面可能解释得不准确,但一般是“方向正确”的,而且可能描绘不全面。如果您发现任何问题或警告,请留下评论,我会尽量调和这个答案。