1. Java模块化与JRE生成背景
Java模块化系统(JPMS)是Java 9引入的重要特性,它从根本上改变了Java应用的打包和部署方式。在模块化之前,Java应用的依赖管理存在几个显著问题:
- 臃肿的rt.jar:Java标准库被打包成单个60MB+的rt.jar文件,即使程序只使用其中一小部分功能,也需要加载整个文件
- 隐式依赖:classpath机制无法明确声明依赖关系,容易导致运行时ClassNotFoundException
- JRE冗余:传统JRE包含全部标准库,而大多数应用只需要其中部分模块
模块化系统通过以下方式解决这些问题:
- 将标准库拆分为独立模块(java.base、java.xml等)
- 引入显式依赖声明(module-info.java)
- 支持按需组合模块生成定制化JRE
2. 模块化JRE生成原理
2.1 模块化JDK结构
安装JDK 9+后,在$JAVA_HOME/jmods目录可以看到核心模块:
java.base.jmod # 基础模块(自动依赖) java.compiler.jmod # 编译器相关 java.desktop.jmod # AWT/Swing java.xml.jmod # XML处理 ...(共约70个模块)每个.jmod文件包含:
- 编译后的class文件
- 本地库文件
- 配置文件
- 模块描述文件(module-info.class)
2.2 jlink工具工作机制
jlink是JDK自带的JRE生成工具,其工作流程如下:
- 解析模块依赖:从根模块出发,递归分析requires依赖
- 优化模块图:去除未使用的模块和代码(通过静态分析)
- 生成镜像:创建包含以下内容的JRE目录:
- 精简的模块集合
- 必要的本地库
- 启动器脚本
- 配置文件
关键优化技术:
- 类层次分析(CHA)去除未实例化的类
- 常量传播消除死代码
- 模块级压缩(压缩重复资源)
3. 实战:生成定制JRE
3.1 环境准备
确保已安装JDK 9+并配置环境变量:
# 检查JDK版本 java -version # 验证jlink可用性 jlink --list-plugins3.2 创建示例模块
项目结构:
helloapp/ ├── src/ │ ├── com.example/ │ │ └── Main.java │ └── module-info.java └── build/module-info.java:
module com.example.hello { requires java.base; requires java.logging; }Main.java:
package com.example; import java.util.logging.Logger; public class Main { private static final Logger LOG = Logger.getLogger(Main.class.getName()); public static void main(String[] args) { LOG.info("Hello Modular JRE!"); } }3.3 编译打包
# 创建输出目录 mkdir -p build/classes # 编译模块 javac -d build/classes \ src/module-info.java \ src/com/example/Main.java # 打包为JAR jar --create \ --file build/helloapp.jar \ --main-class com.example.Main \ -C build/classes .3.4 生成定制JRE
jlink \ --module-path $JAVA_HOME/jmods:build \ --add-modules com.example.hello \ --output build/jre-image \ --strip-debug \ --compress=2 \ --no-header-files \ --no-man-pages参数说明:
--strip-debug:移除调试信息--compress=2:启用ZIP压缩--no-header-files:排除C头文件--no-man-pages:排除手册页
3.5 验证生成的JRE
# 查看生成的大小 du -sh build/jre-image # 运行测试 build/jre-image/bin/java -m com.example.hello/com.example.Main典型输出:
Hello Modular JRE!4. 高级配置技巧
4.1 模块依赖分析
使用jdeps分析模块依赖:
jdeps --print-module-deps build/helloapp.jar输出示例:
java.base,java.logging4.2 包含服务实现
当模块使用ServiceLoader时,需要额外配置:
module com.example.hello { requires java.base; requires java.logging; provides java.util.logging.Handler with com.example.MyCustomHandler; }jlink命令需添加:
--bind-services4.3 多平台打包
使用jmod指定目标平台:
jlink \ --module-path $JAVA_HOME/jmods:build \ --add-modules com.example.hello \ --output build/jre-image-linux \ --platform linux-x645. 性能优化实践
5.1 类数据共享(CDS)
提升启动速度:
# 生成共享归档 build/jre-image/bin/java -Xshare:dump # 使用共享归档 build/jre-image/bin/java -Xshare:on -m com.example.hello/com.example.Main5.2 模块压缩优化
比较不同压缩级别:
| 压缩级别 | 大小(MB) | 启动时间(ms) |
|---|---|---|
| 0 | 45.2 | 120 |
| 1 | 38.7 | 125 |
| 2 | 36.1 | 135 |
推荐生产环境使用--compress=1平衡大小与性能
6. 常见问题排查
6.1 模块找不到错误
错误现象:
Error: Module com.example.hello not found解决方案:
- 检查
--module-path是否包含模块路径 - 确认模块名在module-info.java中正确定义
- 使用
--list-modules验证模块可用性
6.2 类加载失败
错误现象:
java.lang.ClassNotFoundException: javax.xml.parsers.SAXParser解决方案:
- 使用jdeps分析缺失依赖
- 在module-info.java中添加requires子句
- 确保依赖模块包含在jlink的
--add-modules中
6.3 资源文件缺失
错误现象:
MissingResourceException: Can't find bundle解决方案:
- 使用
--include-locales指定包含的区域设置 - 确保资源文件在模块的对应包中
- 通过
Module.getResourceAsStream()加载资源
7. 生产环境建议
7.1 安全实践
- 最小权限原则:只包含必要的模块
- 定期更新:重建JRE以包含安全补丁
- 签名验证:对自定义模块进行代码签名
7.2 持续集成集成
示例Jenkins流水线阶段:
stage('Build JRE') { steps { sh ''' jlink \ --module-path ${JAVA_HOME}/jmods:build/libs \ --add-modules $(jdeps --print-module-deps build/libs/*.jar) \ --output dist/jre \ --strip-debug \ --compress=2 ''' } }7.3 监控与维护
关键监控指标:
- JRE启动时间
- 内存占用
- 模块使用统计(通过JMX)
维护建议:
- 建立模块依赖关系文档
- 为每个应用版本保留对应的JRE构建配置
- 定期评估新JDK版本的模块变化