JMOD的目的并没有得到很好的记录,现有文档也相当稀少。以下是我理解的系统的深入解释。
本回答的部分内容相当冗长、啰嗦、部分重复,阅读起来有些困难。为了改善未来读者的可读性,请提出建设性、结构性或语法性的修改意见。
简短回答
Java 9+中的Java平台模块系统(Jigsaw项目)引入了一个新的可选链接时阶段。使用CLI工具jlink
(JEP 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实用程序jmod
、jimage
和jlink
,以及JMODs/.jmod
s(基于ZIP)和JIMAGEs/.jimage
s的新文件格式。
这个新模块系统的一个重要收获是,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问题作为第二个搜索结果呈现出来。因此,有些方面可能没有被准确地解释,但通常是“方向正确”的;此外,它可能没有描绘全貌。如果您发现任何问题或注意事项,请留下评论,我将尽力与本回答进行协调。
jlink
)。这不是大多数人经常做的事情。它们甚至不是Java SE 9规范的一部分。目前,JMOD文件只是zip文件,但这在未来可能会改变。 - ZhekaKozlovjlink
自己的运行时映像,其中包括您的JMOD模块。不过,我可能还没有完全理解整个情况。 - Anlon Burke