尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

JDK 9+无独立JRE?手把手教你用jlink打造专属精简运行时

JDK 9+无独立JRE?手把手教你用jlink打造专属精简运行时 1. 项目概述从“找不到JRE”到理解现代JDK的变迁最近在技术社区和项目组里经常看到有朋友在问“我明明安装了JDK为什么运行Java程序时提示找不到JRE” 或者 “JDK 11/17/21的安装目录里怎么没有那个熟悉的jre文件夹了”。这确实是一个从Java 9开始随着模块化系统JPMS引入而变得普遍的问题尤其对于习惯了JDK 8及之前版本目录结构的开发者来说算是一个不大不小的“认知冲击”。这个问题的核心远不止是“文件夹去哪了”这么简单它背后是Oracle对Java分发和部署模型的一次重大革新。简单来说从JDK 9起Oracle不再提供独立的JRE安装包并且标准JDK安装包中也移除了独立的jre目录转而鼓励使用更灵活、更精简的运行时映像创建方式。这直接影响了我们日常开发、测试和部署的多个环节。比如你写了一个工具类小程序想分享给没有开发环境的同事运行或者你需要为生产服务器部署一个尽可能小的Java运行时环境以节省资源传统的“复制整个JRE”方法突然就失效了。本篇文章我将从一个多年Java开发者的视角带你彻底理清这个问题。我们不仅会探讨“为什么”更会聚焦于“怎么办”手把手教你使用官方推荐的jlink工具以及一些备选方案来生成你专属的、精简的JRE并解决由此引发的环境配置、命令行工具使用等一系列连锁问题。无论你是正在从JDK 8升级到新版本感到困惑还是初次接触Java 11就遇到了环境障碍这篇文章都能给你提供清晰的路径和可落地的解决方案。2. 核心问题解析为什么JDK不再内置JRE要解决问题首先得理解其根源。在JDK 8及更早的时代一个完整的JDK安装通常包含两个主要部分开发工具如javac,jar,javadoc等和一个独立的JREJava Runtime Environment。这个JRE就放在JDK安装目录下的jre文件夹里。你可以把这个JRE单独拷贝出来放到任何没有安装JDK的机器上用来运行已经编译好的Java程序.jar或.class文件。然而这种模式存在几个显著问题体积庞大且不灵活标准JRE包含了Java SE平台的所有模块即使你的程序只用了其中一小部分比如一个简单的控制台程序根本用不到java.desktopGUI模块你也必须部署整个JRE。维护负担重对于Oracle和用户都需要为完整的JRE提供安全更新和补丁。与模块化理念冲突Java 9引入的模块化系统Project Jigsaw旨在让开发者能明确声明依赖并允许创建只包含所需模块的定制化运行时。因此从JDK 9开始Oracle做出了以下关键改变停止发布独立的JRE安装包你无法再从Oracle官网下载到一个名为“JRE 11”的独立安装程序。JDK安装包中不再包含jre子目录你安装完JDK 11/17/21后在安装目录下只会看到bin,conf,include,jmods,legal,lib等文件夹唯独没有jre。运行时与开发工具包合一JDK本身现在就是一个完整的运行时环境。你可以直接用java -jar yourApp.jar来运行程序因为java命令和所有核心运行时库都已经在JDK的bin和lib目录下了。换句话说现代的JDK本身就是一个“开发版”的JRE。那么当我们需要一个“纯净”的、用于部署的运行时环境时该怎么办答案就是jlink工具。注意这里有一个常见的误解需要澄清。jlink并不是一个“驱动”网络热词中的“jlink驱动安装”通常指的是用于嵌入式开发调试的J-Link仿真器驱动那是Segger公司的一款硬件调试工具与Java的jlink工具同名但完全不同。本文讨论的jlink是JDK自带的命令行工具。3. 解决方案一使用jlink工具创建自定义运行时映像jlink是JDK 9中引入的一个强大工具它允许你基于模块化的Java应用创建一个只包含必要模块的最小化Java运行时映像Custom Runtime Image。这个映像就相当于一个为你应用量身定制的“精简版JRE”。3.1 jlink基础概念与工作原理你可以把jlink想象成一个“Java运行时裁剪器”。它的输入是你的Java应用程序必须模块化或者至少知道依赖哪些Java平台模块以及JDK中的模块存储在$JAVA_HOME/jmods目录下的.jmod文件。jlink会分析你的应用依赖关系然后将这些必需的模块连同java启动器等必要组件打包输出为一个独立的目录。这个目录结构看起来就像一个传统的JRE但它只包含你的应用运行所必需的部件体积可以比完整JRE小很多。3.2 实操步骤为一个简单应用创建精简运行时假设我们有一个最简单的“Hello World”应用它没有使用任何第三方库只依赖于Java标准模块。我们将演示如何为它创建运行时。步骤1准备一个模块化应用或确定依赖如果你的应用是传统的、非模块化的JAR包大多数现有项目如此你仍然可以使用jlink但需要先确定它依赖了哪些Java平台模块。最简单的方法是使用jdeps工具来分析。首先编写一个简单的类并打包# 1. 编写代码 echo public class Hello { public static void main(String[] args) { System.out.println(Hello, Custom Runtime!); } } Hello.java # 2. 编译 javac Hello.java # 3. 打包成可执行JAR非模块化 jar cfe hello.jar Hello Hello.class步骤2使用jdeps分析模块依赖jdeps是JDK自带的依赖分析工具。jdeps -s hello.jar或者为了获取更详细的模块化信息使用jdeps --print-module-deps hello.jar对于我们的Hello.java输出可能很简单如java.base。这意味着这个程序只依赖于最核心的java.base模块。实操心得对于复杂的、使用了大量第三方库的Spring Boot应用jdeps分析可能会比较繁琐因为需要将所有依赖JAR都纳入分析路径。一个更实用的方法是对于Spring Boot打包的包含所有依赖的“fat jar”可以尝试使用jdeps --multi-release 11 --ignore-missing-deps --api-only -cp your-spring-boot-app.jar your-spring-boot-app.jar。但更常见的做法是如果你知道你的应用是一个Web应用除了java.base你很可能还需要java.sql,java.logging,java.xml等模块。这时你可以根据经验手动列出模块。步骤3使用jlink创建运行时映像知道了所需模块例如java.base我们就可以运行jlink命令了。jlink --add-modules java.base --output mycustomjre--add-modules: 指定要包含的模块。可以指定多个用逗号分隔如java.base,java.sql,java.logging。--output: 指定输出目录的名称。执行后当前目录下会生成一个名为mycustomjre的文件夹。查看其大小会发现它远比完整的JDK目录小。步骤4使用自定义运行时运行程序现在你可以使用这个自定义的运行时来运行你的程序而无需系统安装完整的JDK。# 使用自定义运行时中的java命令 mycustomjre/bin/java -jar hello.jar如果一切正常你将看到输出“Hello, Custom Runtime!”。这证明你的精简JRE工作正常。3.3 jlink高级参数与优化基础的jlink命令可能还不够以下是一些常用且重要的参数--compress2: 开启压缩可以进一步减小运行时映像的体积。级别2是资源压缩。--strip-debug: 剥离调试信息能显著减小体积适用于生产环境。--no-header-files和--no-man-pages: 移除C头文件和手册页这些在纯运行时中不需要。--launcher: 创建一个自定义名称的启动脚本。这非常有用可以让你的应用看起来像一个原生应用。jlink --add-modules java.base,java.sql \ --output myapp-runtime \ --launcher myappyour.main.module/your.main.Class执行后在myapp-runtime/bin目录下除了java还会有一个myappWindows下是myapp.bat的启动脚本直接运行它即可启动你的应用。--bind-services: 如果你的模块使用了ServiceLoader机制需要这个参数来链接服务提供者模块。一个综合性的生产环境命令示例jlink --add-modules java.base,java.logging,java.sql,java.xml \ --compress2 \ --strip-debug \ --no-header-files \ --no-man-pages \ --output production-jre4. 解决方案二替代方案与兼容性处理虽然jlink是官方推荐且最符合现代Java理念的方案但在某些过渡期或特定场景下其他方法可能更快捷。4.1 使用JDK自带的“完整”运行时如前所述现代JDK本身就是一个运行时。对于开发和测试环境最简单的方法就是直接使用JDK的bin/java。你只需要确保系统环境变量JAVA_HOME指向你的JDK安装目录并且%JAVA_HOME%\binWindows或$JAVA_HOME/binLinux/Mac被添加到PATH环境变量中。环境变量配置要点以Windows为例新建系统变量JAVA_HOME值设为你的JDK安装路径例如C:\Program Files\Java\jdk-17。编辑系统变量Path添加一个新条目%JAVA_HOME%\bin。打开一个新的命令行窗口cmd输入java -version和javac -version验证。两者都应正确显示版本信息。常见问题排查“为什么WinR打开cmd后java命令仍不可用” 这通常是因为修改环境变量后没有重新启动命令行窗口。已打开的cmd会话继承的是旧的环境变量需要关闭后重新打开。如果重启cmd仍无效检查Path变量中是否有其他旧版本Java路径在前导致了冲突。4.2 寻找第三方提供的JRE构建一些OpenJDK的发行版提供商如Adoptium原AdoptOpenJDK、Azul Zulu、Amazon Corretto等在某些版本中可能仍提供独立的JRE下载或者提供更便捷的构建自定义运行时的工具。例如jlink的图形化前端工具或与构建工具Maven/Gradle的集成插件。Zulu JDKAzul Systems提供的Zulu OpenJDK发行版在其下载页面上有时会明确区分JDK和JRE包对于某些需要传统JRE格式的场景可以在这里寻找。构建工具集成在Maven项目中可以使用maven-jlink-plugin插件在打包阶段自动生成与应用对应的自定义运行时并与你的应用JAR一起打包例如生成一个包含运行时的镜像或制作成原生安装包。4.3 处理遗留软件与非模块化应用的依赖最大的挑战来自于那些遗留的、非模块化的Java应用或者需要动态加载类的复杂应用如某些应用服务器、IDE插件系统。它们可能在运行时需要访问大量未声明的模块。方法一使用--illegal-access参数过渡方案在JDK 9到16中你可以使用--illegal-accesspermit来允许对内部API的非反射访问但这只是一个临时方案并且在JDK 17中该参数的默认值已变为deny且permit选项已被移除。方法二在jlink中添加可能需要的模块如果使用jlink对于不确定的依赖一个保守的策略是添加更多你可能需要的模块。例如一个典型的Web应用可能需要以下模块java.base java.logging java.sql java.xml java.naming java.management java.security.jgss java.instrument java.se (这个聚合模块包含了Java SE的全部模块但会使得映像变大失去了定制化的意义)方法三使用完整的JDK作为运行时在最终确定模块列表前最稳妥的部署方案就是直接使用完整的JDK作为生产环境运行时。虽然体积大但保证了100%的兼容性。在磁盘空间不那么敏感的场景下这通常是阻力最小的路径。5. 实战演练为Spring Boot应用构建定制化运行时现代企业级应用很多基于Spring Boot。Spring Boot 2.3 对jlink和原生镜像GraalVM有了更好的支持。这里我们演示一个简化流程。前提你的Spring Boot应用必须是一个模块化应用拥有module-info.java文件。Spring Boot 2.7 支持创建带有模块描述符的应用。步骤1创建模块化的Spring Boot应用并打包确保你的pom.xml或build.gradle配置正确并能生成一个可执行的、模块化的JAR文件。步骤2使用jlink插件Maven示例在pom.xml中添加maven-jlink-plugin配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jlink-plugin/artifactId version3.1.0/version configuration !-- 指定你的应用模块名 -- modulecom.example.myapp/module !-- 指定启动器格式启动器名模块名/主类 -- launchermyappcom.example.myapp/com.example.myapp.MyApplication/launcher !-- 输出目录 -- outputDirectorytarget/myapp-runtime/outputDirectory !-- 压缩选项 -- compress2/compress stripDebugtrue/stripDebug noHeaderFilestrue/noHeaderFiles noManPagestrue/noManPages /configuration executions execution idcreate-runtime/id goalsgoaljlink/goal/goals phasepackage/phase !-- 绑定到package阶段打包时自动执行 -- /execution /executions /plugin注意你需要通过jdeps或分析确保module标签里列出的模块包含了所有依赖的Java平台模块。插件可能会尝试自动分析但对于复杂依赖可能需要你在configuration中通过addModules手动添加。步骤3运行打包命令mvn clean package命令执行成功后在target目录下除了普通的.jar文件你还会看到一个myapp-runtime目录这就是为你Spring Boot应用定制的运行时。你可以将这个目录整个拷贝到部署服务器上。步骤4部署与运行在服务器上无需安装任何JDK直接运行# Linux/Mac target/myapp-runtime/bin/myapp # Windows target/myapp-runtime\bin\myapp.bat踩坑记录在实践中最大的难点在于准确识别Spring Boot应用及其所有传递依赖包括第三方库所必需的Java平台模块。Spring Boot的自动配置和反射大量使用可能会在运行时动态加载类。一个模块列表的缺失可能导致应用在jlink生成的运行时中启动失败报错ClassNotFoundException或ModuleNotFoundException。因此建议先在测试环境中使用完整JDK运行应用通过-XX:TraceModuleLoadingJDK 9-16或-Xlog:modulestraceJDK 17等JVM参数来跟踪实际加载的模块作为jlink模块列表的重要参考。6. 常见问题排查与操作技巧实录在这一部分我汇总了在解决“JDK无JRE”问题以及使用jlink过程中最常遇到的一些“坑”和对应的解决方法。6.1 环境与命令相关问题问题1在CMD中执行java或javac命令提示“不是内部或外部命令”原因PATH环境变量未正确配置或配置后未重启CMD。解决确认JAVA_HOME变量已设置且路径正确末尾不要有\bin。确认Path变量中包含%JAVA_HOME%\bin。关闭所有已打开的CMD窗口重新打开一个新的。这是最关键的一步。在新CMD中输入echo %JAVA_HOME%和where java来验证。问题2系统安装了多个Java版本如何切换解决临时切换在CMD中直接设置PATH。例如想用JDK 11set PATHC:\Program Files\Java\jdk-11.0.xx\bin;%PATH%。永久切换调整系统环境变量JAVA_HOME和Path中Java路径的顺序将需要的版本路径置前。使用工具在Windows上可以使用JEnv或第三方工具在Linux/Mac上update-alternatives是标准管理工具。问题3使用jlink时提示“模块XXX未找到”原因--add-modules参数中指定的模块名错误或者该模块在$JAVA_HOME/jmods中确实不存在。解决检查模块名拼写。Java平台模块名均为小写如java.base。列出所有可用模块进行核对java --list-modules。注意有些模块是聚合模块如java.se包含了其他模块。如果你添加了java.se就不需要再单独添加其子模块。6.2 jlink使用中的典型错误问题4用jlink生成的运行时运行非模块化JAR失败现象运行时报错提示缺少java.sql、java.xml等模块即使你的代码没直接使用它们。原因你使用的第三方库如数据库驱动、XML解析器在运行时需要这些模块但jdeps在静态分析时可能没有捕获到。解决保守策略根据经验将常见的平台模块如java.sql,java.xml,java.logging,java.naming加入jlink命令。动态分析使用完整JDK运行你的应用并添加JVM参数-Xlog:modulestraceJDK 17来记录所有模块加载活动从中提取出必需的模块列表。使用--bind-services参数确保服务加载机制正常工作。问题5生成的运行时映像体积仍然很大优化确保使用了--strip-debug和--compress2。仔细审查--add-modules列表移除确实不需要的模块。例如纯后台服务可能不需要java.desktop。使用--no-header-files和--no-man-pages。考虑使用jlink的--exclude-files选项排除特定区域的文件需谨慎。6.3 部署与运行阶段问题问题6在目标机器上双击自定义运行时bin目录下的启动脚本无反应原因Windows常见.bat脚本可能因为路径包含空格或特殊字符而执行失败或者缺少必要的VC运行库通常不需要。解决打开CMDcd到该bin目录手动执行.bat脚本查看具体错误信息。检查脚本内容确保java命令的路径正确。jlink生成的脚本通常是相对路径应能正常工作。确保目标机器满足Java应用的基本要求如正确的系统架构x64/arm64。问题7应用在自定义运行时中性能异常或出现未预期行为原因可能缺少了某些用于性能监控或管理的模块如jdk.management,jdk.attach或者某些基于反射的框架如Spring、Hibernate在精简环境中行为有差异。解决对比在完整JDK和自定义运行时下的运行日志。尝试将jdk.management、jdk.attach等模块加入运行时。对于复杂框架考虑在测试阶段使用一个“稍大”的运行时包含java.se确保功能正常后再尝试逐步精简。最后关于网络热词中提到的“jdk降级到17”这通常指从更新的版本如JDK 21回退到JDK 17。操作上就是卸载新版本安装JDK 17并更新环境变量。选择JDK 17作为长期支持LTS版本是一个稳妥的选择它在功能、性能和生态支持上取得了很好的平衡。而“jdk21有jre吗”的答案很明确没有独立的JRE安装包但你可以用上述方法从JDK 21轻松创建出属于你自己的、适配你应用的“JRE”。这个过程正是现代Java拥抱模块化、追求高效部署的体现。掌握jlink你就掌握了为新版本Java应用打造轻量级部署容器的钥匙。
返回列表