--- title: 使用jlink裁剪Java运行时 tags: - JVM - Java - jlink - JDK - JRE - JPMS categories: - - JVM - Java keywords: 'Java,jlink,jre,运行时,裁剪,精简' date: 2021-05-31 14:11:31 --- jlink是一个从Java 9就开始存在的命令行工具,其主要功能就是用来创建一个可执行的Java运行时镜像。jlink是随着模块系统加入到Java中的,所以jlink在使用的时候也是需要搭配JPMS来使用的。 具体基于JPMS的应用构建可以参考文章{% post_link nf-java9 %}。 ## 应用打包存在的问题 在Java程序中,一个类在编译和运行的过程中需要使用到其他的类是十分正常的事情,但是这里就有一个十分明显的问题存在:支持Java程序运行的JRE中包含着许许多多的类,但是程序可能只用到了其中寥寥的几个类。比如一个只需要在命令行中运行的程序,它的JRE中却包含了支持GUI的`javax.swing`包。 而且在Java程序打包的时候,第三方库的类文件,是无法将他们自己的API类文件和内部类文件从不同的包中分离的。 > 当然,包的可见性的问题现在已经通过引入JPMS来解决了。 ## jlink工具的使用 一个采用了JPMS的应用,都是采用位于其根目录下的`module-info.class`文件描述其模块化信息的,这个文件里包含了模块的名称、依赖模块的声明等。正是这个文件的存在,才使得应用在运行的时候,Java加载器可以只加载必要的模块。而且也就是这个文件的存在,可以支持对JDK进行剪裁,定制出一个最适合应用运行的最小JRE。 这个剪裁JDK的任务,就是jlink工具完成的。jlink将最终创建一个可执行文件,使最终可交付的应用能够自给自足,使用一个不依赖于系统的JRE环境。 jlink工具的命令格式是这样的: ```bash jlink [options] --module-path --add-modules ``` 首先来从一个示例来看一下jlink命令的使用。 ```bash jlink --add-modules xyz.archgrid.demo.jlink.app \ --module-path ${JAVA_HOME}/jmods:target/jlink-lib-groups-1.0.jar \ --output target/jlink-image \ --launcher starter=xyz.archgrid.demo.jlink.app/xyz.archgrid.demo.jlink.app.Main ``` 在这个示例中,各行的功能含义都是比较明确的: 1. `--add-modules`定义所需的模块,一般应用的模块名称需要被书写在这里。 1. `--module-path`定义组成JRE的模块路径。在Java 9之前使用的是`classpath`,兼容的模块则使用模块路径。jlink使用这里所列举的路径来发现应用所使用的模块,这些模块可以是模块化的Jar文件,也可以是JMOD文件,或者是打散的模块化文件。 1. `--output`指定输出文件的目录。 1. `--launcher`指定构建的入口点,其中包括最终可执行文件的名称、模块名称和主函数的类路径和类名。 > 注意,`--module-path`中的路径列表,在Linux和macOS系统中,多个路径之间是使用`:`隔开的,而在Windows系统中,是使用`;`隔开的。 对于这个示例命令,其在执行完构建之后,可以通过`target/jlink-image/bin/starter`来启动构建之后的应用。 jlink命令中其他常用的选项主要有以下这些。 * `--bind-service`,将服务提供者模块与其依赖连接起来。 * `--compress=<0|1|2>`,对资源内容进行压缩。 * `0`表示不压缩。 * `1`表示使用共享字符串常量的方式压缩。 * `2`表示使用ZIP压缩。 * `--endian `,设定生成镜像的字节序。 * `--launcher command=module`或者`--launcher command=module/main`,设定模块的启动程序命令。 * `--limit-modules `,限制可观察模块的范围。 * `--no-header-files`,排除所有饿头文件。 * `--no-man-files`,排除所有的man说明文件。 * `--save-opts `,将jlink使用的选项都记录在指定文件中。 * `@`,从指定文件中读取选项记录。