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

87

Java 9有三种打包编译代码的方式:

  • JAR
  • JMOD
  • JIMAGE

JIMAGE是为速度和空间优化而设计的,由JVM在运行时使用,因此引入JIMAGE是很有意义的。不能将JIMAGE文件发布到maven仓库或在编译或链接时使用。

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

问题:

  • 为什么Java 9引入了JMOD文件格式?
  • 库作者应该分发JMOD文件还是JAR文件或两者都应该有?
  • 是否应该将jmod文件发布到maven仓库?

2
“JMOD文件可用于编译时和链接时,但不能在运行时使用。” 因此,我猜除非库使用者想要链接自己的运行时映像,否则JAR仍然更有用。 - Stefan Zobel
1
据我所知,JMOD文件仅用于构建自定义运行时映像(使用jlink)。这不是大多数人经常做的事情。它们甚至不是Java SE 9规范的一部分。目前,JMOD文件只是zip文件,但这在未来可能会改变。 - ZhekaKozlov
2
@ZhekaKozlov 在Java 9中已经没有jre/lib/rt.jar了,所以当javac运行时查找java.util.List的代码时,它会从$JDK_HOME/jmods/java.base.jmod中获取,因此我认为如果您只关心Java 9,可以只分发JMOD文件而不是jar文件。 JMOD文件在JVM运行时不会被读取,因此如果您使用jlink创建自定义jdk映像以供客户使用,则包含JMOD文件是没有意义的,Java 9 JRE不会随JDK的JMOD文件一起提供。 - ams
3
抱歉,我不理解你上一条评论的意思。你是在暗示Java 9 JDK在运行时从JMOD文件中读取吗?据我所知,那是不正确的。你可以从JDK中删除整个jmods目录而没有任何后果。另外,据我所知,Java 9 JDK不会“获取”你分发的自定义JMOD。 - Anlon Burke
3
据我所知(正如其他评论者在此处所说的),您需要发布一个JMOD文件,以使消费者能够jlink自己的运行时映像,其中包括您的JMOD模块。不过,我可能还没有完全理解整个情况。 - Anlon Burke
显示剩余2条评论
2个回答

70

JMOD的目的并没有得到很好的记录,现有文档也相当稀少。以下是我理解的系统的深入解释。

本回答的部分内容相当冗长、啰嗦、部分重复,阅读起来有些困难。为了改善未来读者的可读性,请提出建设性、结构性或语法性的修改意见。


简短回答

Java 9+中的Java平台模块系统Jigsaw项目)引入了一个新的可选链接时阶段。使用CLI工具jlinkJEP 282)构建自定义空间优化的JRE时,将会出现这个阶段。

jlink实用程序将所有显式/传递的JAR模块/JMOD依赖项捆绑到一个缩小的JRE中。从指定的根模块开始,依赖图中所有其他无法访问的依赖项都会被省略在构建的JRE中。从JDK 9+开始,Java的整个标准库都已分解为JMOD。这些JMOD位于<jdk>/jmods中。

与JAR只能包含.class和资源文件不同,JMOD(即.jmod文件)包含特定于新可选的链接时阶段的其他文件,以自定义JRE(例如可执行文件、本地库、配置、法律许可等)。这些附加文件在类路径上运行时不可用作资源,而是安装在构建的JRE的各个位置下(例如可执行文件和本地库位于<jre>/bin下)。从相关的捆绑JAR和JMOD依赖项中,类和文件资源将被写入单个优化的JIMAGE文件,位于<jre>/lib/modules中(在Java 8及之前版本中替换<jre>/lib/rt.jar)。JMOD的作用是在编译时和链接时间,不适用于运行时。

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

长答案

这个冗长的答案更加复杂地阐述了新模块系统的基本操作方式。本文强调CLI工具jlink,因为JMOD专门为该工具引入的新的可选的链接时间阶段设计。

项目Jigsaw的介绍

Java 9在“JEP 261: 模块系统”中引入了Project Jigsaw,这是一个新颖的模块系统,可以用于最小化启动时间和JRE的大小。作为此版本的一部分,还引入了CLI实用程序jmodjimagejlink,以及JMODs/.jmods(基于ZIP)和JIMAGEs/.jimages的新文件格式。

这个新模块系统的一个重要收获是,CLI工具jlink使开发人员能够构建仅包含其应用程序所需的相关标准库和外部依赖项的自定义JRE。这引入了传统编译时间 -> 运行时间流水线中一个可选的新的链接时间阶段的概念。

使用jlink的优点之一是,从仅包含java.base模块的JDK 15构建的极简JRE大约为40MB,与JDK 15的大约310MB大小形成了鲜明对比。这对于发布最小化的自定义JRE(例如用于精简的Docker映像)非常有用。新的模块系统为Java生态系统带来了重大的好处,这些好处已经在其他地方详细讨论过,因此在此不再详细阐述。

3个J:JAR、JMOD和JIMAGE

JAR、JMOD和JIMAGE的高层描述并不快速地借助于一个解释,强烈区分三种文件格式的角色。这里是每个文件格式目的的非详尽概述:

  • JARs: 基于ZIP文件格式的经典格式,用于在运行时将类和资源捆绑到类路径中。自1997年JDK 1.1以来,这是事实上的主流标准。可以使用java -cp/-classpath标志将JAR添加到类路径中。几乎每个库或依赖项都有使用此格式,因此在本节中略过。

  • JMODs: 一种基于ZIP文件格式的新格式,用于捆绑与JAR相同的内容,但支持其他文件(例如可执行文件、本机库、配置、法律许可等),这些文件在构建自定义JRE时在可选的链接时间阶段被消耗。JMOD旨在在编译时和链接时使用,但不应在运行时使用。可能引入了这种新格式(而不是扩展JAR),因为该新基于存档的格式中的目录具有特殊含义,这与已经使用相同目录名称的JAR不兼容。

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

注意:可能有一种方法可以在运行时轻松地将JMOD添加到类路径中;然而,研究并没有明确说明与此相关的任何功能。仅仅添加一个JMOD到类路径中是不足以使用其中的类和资源的。可以使用自定义ClassLoader在运行时正确解析JMOD存档中的类和资源文件,但这通常不被推荐,并且不是JMOD的目的。

  • JIMAGEs: 一种特殊的文件格式,引入自'JEP 220: 模块化运行时映像',是一个包含JRE(即标准库)所有必要类和资源的运行时映像。在JRE/JDK 9之前,使用单个大型非模块化的超级JAR,位于<jre>/lib/rt.jar; 现已被单个优化的JIMAGE所取代,存储位置为<jre>/lib/modules。该格式不基于ZIP格式,而是使用自定义格式,比原始传统JAR格式更加时间和空间有效,从而降低启动时间。
    • 使用CLI工具jlink构建自定义的JRE映像时,所有相关(显式或传递)模块依赖项的类和资源(来自JAR模块或JMOD)都编译成单个优化的JIMAGE文件(再次存储在<jre>/lib/modules下)。
    • JIMAGE文件格式是模块化的,可以使用CLI工具jimage创建、修改、反汇编或检查。例如:jimage list $JAVA_HOME/lib/modules
    • 通常不应发布JIMAGEs,而应随特定的自定义JRE版本一起提供;文件格式可能在未来发生变化。

The Substance: JMOD的详细目的

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

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

高级阶段简要描述如下:

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

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

  • 链接时 (jlink): 如'JEP 282:jlink:Java链接器'所述,链接时阶段是...

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

    链接时间是进行整体优化的机会,在编译时很难实现或在运行时成本高昂。例如,当所有输入变量都变为常量(即不是未知的)时,可以优化计算。后续的优化将是删除不再可达的代码。

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

    ...启动Java应用程序。它通过启动Java运行时环境(JRE),加载指定的类,并调用该类的main()方法来实现。

JMODs简介

在链接时阶段,来自模块(有效的JAR模块或来自JMOD的classes)的所有类和资源都编译成单个优化的JIMAGE运行时映像,位于<jre>/lib/modules。未明确或传递包含的模块将不会包括在此最终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文件格式类似于基于ZIP文件格式的JAR。 JMOD文件具有以下保留目录名称及其行为(这不一定是详尽无遗的列表!):
- bin(--cmds):可执行命令,将复制到/bin中。 - classes(--class-path):用于包含到最终构建的JIMAGE中,存储在/lib/modules中。 - conf(--config):附加配置,将复制到/conf中;如果需要,可能用于控制任何捆绑模块的配置。 - include(--header-files):其他C头文件,将复制到/include/中,用于使用JNI构建JVM的C库;例如,在java.base中,导出了JNI接口。 - legal(--legal-notices):模块的法律声明和许可证,将复制到/legal//中。 - lib(--libs):本地库,将复制到/bin中。
对于那些有好奇心的人,标准库JMOD(位于JDK 9+中的$JAVA_HOME/jmods下)可以用任何读取ZIP档案的应用程序进行检查。
主流支持...?
JMODs之所以没有被迅速采用并且文档可用性差,其中一个重要原因是对于绝大多数库和模块依赖来说,它们根本不是必需的。虽然它们仍然对于特定用例非常有用,但模块应该使用已经具有主流支持的JAR格式,自从1997年在JDK 1.1中定义以来就一直如此(使用JDK 9在2017年添加了module-info.java模块支持)。
从CLI工具jmod的文档中:
对于大多数开发任务,包括将模块部署到模块路径上或将其发布到Maven存储库中,请继续将模块打包成模块化JAR文件。jmod工具适用于具有本地库或其他配置文件的模块,或者您打算使用jlink工具将其链接到运行时映像的模块。
一个观点:JMODs可能不会在至少很长一段时间内得到开发者的广泛采用。大多数开发者将永远不会听说或知道JMOD的目的,也不需要知道。JMOD在幕后为构建JRE提供了关键作用(所有Java标准库模块都是JMODs),但由于它们在链接时的利基用例,它们不会影响绝大多数应用程序和项目。 Java 9于2017年发布,Java生态系统中的依赖项仍然难以可靠地具有module-info.class描述符,使JAR成为有效的完整模块...
要点
- JMOD是使用CLI工具jlink创建自定义构建JRE的基本新功能,该工具允许使用附加文件自定义构建的JRE。 - 部署JAR而不是JMOD,除非特定需要JMOD的某些功能。 JAR模块也与jlink兼容,因此不必发出仅包含类和资源的JMOD。生态系统支持和工具并不一定会很快采用JMOD,并且肯定会在未来几年遇到兼容性问题。 - Java生态系统中这个领域的文档确实需要改进。

免责声明

在撰写本答案时,关于Java 9及其后续版本中JMOD的目的的文档非常稀少。事实上,谷歌搜索短语“java jmods”和“jmod format”将会把这个StackOverflow问题作为第二个搜索结果呈现出来。因此,有些方面可能没有被准确地解释,但通常是“方向正确”的;此外,它可能没有描绘全貌。如果您发现任何问题或注意事项,请留下评论,我将尽力与本回答进行协调。


1
请注意,“链接”一词也用于类加载期间的一个阶段,在运行时。有关详细信息,请参见此答案,以及Java语言规范 - Lii

14

以下是来自JEP 261: 模块系统的一些引用,其中包含了有关JMOD文件的部分。

为什么需要 JMOD 文件?

来自JEP 261:

新的 JMOD 格式不仅仅可以包含 JAR 文件,还可以包含本地代码、配置文件以及其他类型的数据,这些数据并不自然地或者根本就无法放在 JAR 文件中。

以及

JMOD 文件的最终格式是一个开放性问题,但目前基于 ZIP 文件。

开发人员是否应该发布 JMOD 文件?

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

JMOD 文件可用于编译时和链接时,但不适用于运行时。

说实话,在JDK 9之前,我不确定本地代码是如何发布的。对于绝大多数开发者(没有本地库或其他边缘情况),我们将仅发布模块化的JAR包。


11
换句话说,JMOD文件本不应该是zip文件,但由于资源不足,只能采用这种笨拙的方法。虽然承诺会在未来提供更好的解决方案,但在接下来的十年内这种情况并不会发生。 - Holger
1
本地代码曾经以共享库的形式发布,即平台本地形式。 - Thorbjørn Ravn Andersen
1
我的项目捆绑了一些本地库(https://dev59.com/0lQJ5IYBdhLWcg3w356R),lib/jar依赖项只是与本地对象捆绑在一起,并找到一种在运行时加载它们的方法。由于该项目正在从JDK8迁移到JDK11,原因是最近发生的事件(https://www.azul.com/think-moving-jdk-9-sometime-next-year-think/),而JavaFX运行时(现在已经丢失)提供了几种格式,其中之一是“jmod”。我想知道这种格式是否有助于依赖关系/打包过程。 - tresf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接