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

资讯详情

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

JDK 9+为何不再内置JRE?从模块化原理到实战解决方案

JDK 9+为何不再内置JRE?从模块化原理到实战解决方案 1. 从一次部署失败说起为什么我的JDK里没有JRE那天下午我正在为一个老旧的内部系统打补丁。系统要求Java 8环境我熟练地从Oracle官网下载了最新的JDK 8u401安装包一路“下一步”完成安装。然而当我尝试运行一个需要独立JRE环境的第三方工具时熟悉的错误弹窗让我愣住了“找不到Java运行时环境JRE”。我下意识地打开JDK的安装目录在bin的同级目录下反复寻找那个本该存在的jre文件夹竟然消失了。这不是我第一次遇到这个问题但每次都有新的开发者掉进这个“坑”里。从JDK 9开始Oracle就正式改变了发行策略不再在JDK安装包中默认包含一个独立的JRE目录。这个变化看似微小却影响深远尤其对于那些依赖特定目录结构的老旧脚本、需要独立分发运行时的项目或者习惯了旧版配置方式的开发者。如果你正在为“JDK没有JRE”而困惑或者你的构建脚本因为找不到%JAVA_HOME%/jre/bin而报错那么这篇文章正是为你准备的。我将带你彻底理解这一变化的来龙去脉并分享几种经过实战检验的解决方案从临时救急到一劳永逸总有一款适合你。2. 历史与现状JDK与JRE的“分分合合”要解决问题首先得理解问题的根源。JDKJava Development Kit和JREJava Runtime Environment的关系在Java的发展史上经历了几次重要的演变。2.1 传统的“全家桶”模式JDK 8及以前在JDK 8及更早的版本中Oracle的安装模式非常直观安装JDK附赠一个完整的JRE。当你安装完JDK后其目录结构通常是这样的C:\Program Files\Java\jdk1.8.0_401\ ├── bin/ # 开发工具 (javac, jar, javadoc等) ├── lib/ # 开发库 ├── jre/ # **独立的JRE目录** │ ├── bin/ # 运行时工具 (java, javaw等) │ └── lib/ # 运行时库 (rt.jar等) └── ...这种结构下JAVA_HOME通常指向jdk1.8.0_401而许多需要运行Java程序的脚本或工具则会去%JAVA_HOME%\jre\bin里找java.exe。这个独立的JRE包含了运行Java程序所需的最小环境但不包含编译器javac等开发工具。你可以单独安装JRE也可以从JDK中获得它两者并行不悖。2.2 模块化带来的变革JDK 9及以后随着Java 9引入模块化系统Project Jigsaw一切都变了。模块化的核心思想是“按需组装”旨在解决JAR地狱和运行时臃肿的问题。在这一理念下继续打包一个独立的、完整的JRE就显得冗余了。因此从JDK 9开始Oracle官方发布的安装包包括后来的JDK 11、17、21等LTS版本不再默认生成或包含一个独立的jre文件夹。JDK本身就是一个完整的运行时环境。你现在安装的JDK目录结构是这样的C:\Program Files\Java\jdk-17.0.10\ ├── bin/ # 包含所有工具javac, java, jar, jshell... ├── lib/ # 包含所有模块jmods目录和库 └── ...注意java命令运行时和javac命令编译器现在并列位于bin目录下。JDK本身就是一个“超级”运行时。所谓的“JRE”功能已经通过bin目录下的java和相关lib目录下的模块实现了。注意这里有一个常见的误解需要澄清“JDK 21有JRE吗” 答案是从功能上说有从目录结构上说没有。JDK 21的bin\java.exe就是运行时你不需要一个单独的jre文件夹。所谓的“JRE精简版”概念在模块化时代被jlink工具创建的自定义运行时镜像所取代。2.3 为什么我们还会“找不到JRE”既然新JDK已经包含了运行时为什么问题依然存在根本原因在于路径依赖和兼容性。老旧软件或脚本很多企业级软件、历史遗留的部署脚本、自动化工具如一些旧的Jenkins插件或构建脚本硬编码了%JAVA_HOME%/jre/bin或$JAVA_HOME/jre/bin这样的路径。当它们运行在JDK 9的环境时就会因为路径不存在而失败。环境变量配置一些教程或习惯将PATH环境变量指向%JAVA_HOME%/jre/bin而不是%JAVA_HOME%/bin。在新JDK下这会导致命令行中java命令无法识别。心理惯性开发者习惯了旧有的目录结构在排查问题时会下意识地去寻找那个不存在的jre文件夹从而产生困惑。理解了这些我们就知道解决方案的核心无非两点一是让旧路径指向新位置兼容性方案二是创建出一个符合旧结构的JRE结构性方案。3. 解决方案一环境变量与符号链接——快速兼容之道这是最简单、最快速的解决方法尤其适合临时救急或个人开发环境。其核心思想是通过配置让系统或软件在寻找旧路径时能够自动重定向到新的正确位置。3.1 修正系统环境变量Windows/Linux/macOS通用这是首要检查项。很多“找不到Java”的问题根源在于环境变量配置错误。检查JAVA_HOME确保JAVA_HOME变量指向的是你的JDK安装根目录例如C:\Program Files\Java\jdk-17.0.10或/usr/lib/jvm/jdk-17。不要指向任何jre子目录。修正PATH变量将%JAVA_HOME%\binWindows或$JAVA_HOME/binLinux/macOS添加到系统的PATH环境变量中并确保其优先级较高。绝对不要添加.../jre/bin。验证配置打开新的终端或命令提示符执行以下命令# 检查java版本 java -version # 检查编译器版本 javac -version如果两者都能正确输出版本信息且版本符合你的JDK说明环境变量配置正确。如果java命令失败而javac成功几乎可以断定PATH指向了错误的bin目录可能指向了旧的、单独安装的JRE。3.2 创建目录符号链接Windows/Linux/macOS对于那个硬编码了jre目录的顽固脚本我们可以“欺骗”它。通过创建一个名为jre的符号链接Symlink将其指向JDK的根目录或bin的父目录这样脚本访问jre/bin时实际上访问的是JDK/bin。在Windows上需要管理员权限打开命令提示符CMD或PowerShell管理员进入你的JDK安装目录例如cd C:\Program Files\Java\jdk-17.0.10 # 创建名为jre的目录联接指向当前目录即JDK根目录 mklink /J jre .执行后你会看到JDK目录下出现一个名为jre的文件夹快捷方式类型为“联接”。此时脚本访问C:\Program Files\Java\jdk-17.0.10\jre\bin\java.exe实际上访问的就是C:\Program Files\Java\jdk-17.0.10\bin\java.exe。在Linux/macOS上打开终端进入你的JDK安装目录例如cd /usr/lib/jvm/jdk-17 # 创建名为jre的符号链接指向当前目录 sudo ln -s . jre创建后/usr/lib/jvm/jdk-17/jre/bin/java将指向/usr/lib/jvm/jdk-17/bin/java。实操心得符号链接是一个非常优雅的解决方案它几乎不占用磁盘空间且对系统无侵入性。当未来升级JDK时只需删除旧链接在新JDK目录下重新创建即可。这是解决目录结构依赖问题的首选方案。3.3 特定IDE配置以IntelliJ IDEA为例有时问题出在IDE内部。例如在IDEA中运行或调试项目时它可能错误地引用了一个不存在的JRE路径。打开 IntelliJ IDEA进入File-Project Structure(CtrlAltShiftS)。在Project设置中确保Project SDK指向一个有效的JDK 9版本而不是一个“JRE”。在Modules设置中检查每个模块的Dependencies标签页确保Module SDK同样指向正确的JDK。在Run/Debug Configurations中检查每个运行配置的JRE选项确保它使用的是项目SDK或一个有效的JDK路径。踩坑记录我曾经遇到一个Maven项目在IDEA里运行报错提示JRE丢失。排查后发现是某个古老的pom.xml文件里通过maven-surefire-plugin插件硬编码了jvm${java.home}/jre/bin/java/jvm。在JDK 8下正常在JDK 11下就失败了。解决方法是在插件配置中移除这个指定或者将其改为jvm${java.home}/bin/java/jvm。4. 解决方案二使用jlink生成自定义运行时——终极灵活方案如果你需要一个物理上独立存在的、精简的JRE或者要为你的应用程序分发一个量身定制的运行时环境那么jlink工具是你的不二之选。它是Java 9模块化带来的官方“JRE生成器”。4.1 jlink是什么为什么是终极方案jlinkJava Linker允许你通过组合JDK中的模块jmod文件创建一个只包含应用程序所需模块的最小化Java运行时镜像。这个镜像的目录结构与传统JRE高度相似。这意味着你可以“造”出一个JRE生成一个包含bin/java和必要库的目录完美满足老旧脚本的路径要求。体积大幅减小一个完整的JDK 17大约300MB而一个只包含java.base等核心模块的运行时可能只有40MB左右。安全性与维护性只包含用到的模块减少了攻击面。你可以为每个应用创建独立的运行时避免环境冲突。4.2 手把手使用jlink创建运行时镜像假设我们有一个最简单的“Hello World”应用它只依赖于Java标准库的基础模块。列出可用模块首先查看你的JDK提供了哪些模块。# 进入JDK安装目录的bin文件夹 cd %JAVA_HOME%\bin # 列出所有模块 java --list-modules你会看到一串以java.、jdk.开头的模块名例如java.base、java.desktop、java.sql等。使用jlink命令创建镜像我们创建一个包含最基础模块java.base和必要的工具模块jdk.jdwp.agent用于调试支持可选的运行时。jlink --module-path %JAVA_HOME%\jmods ^ --add-modules java.base,jdk.jdwp.agent ^ --output my-custom-jre ^ --strip-debug ^ --compress2 ^ --no-header-files ^ --no-man-pages参数解析--module-path指定模块路径指向JDK的jmods目录。--add-modules指定要包含的模块。java.base是核心必不可少。--output指定输出目录名称这里我们叫my-custom-jre。--strip-debug移除调试信息减小体积。--compress2启用ZIP压缩进一步减小体积。--no-header-files --no-man-pages移除C头文件和帮助手册这些在运行时不需要。检查生成的“JRE”命令执行成功后当前目录下会生成一个my-custom-jre文件夹。其结构如下my-custom-jre/ ├── bin/ │ ├── java │ └── ... (其他可选工具如keytool取决于包含的模块) ├── conf/ # 配置文件 ├── lib/ # 运行时库模块化后的 └── legal/ # 法律声明看一个熟悉的“JRE”结构出现了现在你可以将JAVA_HOME指向这个my-custom-jre目录或者将my-custom-jre/bin加入PATH所有依赖旧路径的脚本都能正常运行了。4.3 为真实应用构建运行时对于真实的应用程序你需要知道它依赖哪些模块。有几种方法使用jdeps分析JDK自带的jdeps工具可以分析JAR包或类文件的模块依赖。# 分析你的应用jar包 jdeps --list-deps your-application.jar输出会列出所需的模块。将这些模块名加入到jlink的--add-modules参数中。包含所有非JDK模块如果你的应用使用了第三方库非模块化的JAR情况更复杂。你需要将这些JAR放到一个目录如libs然后使用--module-path同时指定JDK的jmods和你的libs目录并使用--add-modules ALL-MODULE-PATH谨慎使用可能会包含过多模块。注意事项jlink生成的运行时是平台相关的Windows版不能在Linux上运行。你需要为每个目标平台单独生成。对于复杂的、依赖大量第三方库的非模块化应用使用jlink可能会遇到挑战这时可能需要结合jpackage用于打包原生安装程序或考虑其他方案。5. 解决方案三回归与替代——安装传统JRE或选用特定JDK发行版如果上述方案对你来说都太复杂或者你面对的是一个完全无法修改的封闭环境还有更直接的“回归”方案。5.1 安装传统的Oracle JRE 8对于必须使用Java 8且必须存在独立JRE目录的极端场景最省事的办法就是直接安装一个Oracle JRE 8。你仍然可以从Oracle官网的Java存档页面找到历史版本的JRE 8安装程序。安装后你会得到一个标准的、带有jre目录的Java运行时环境。重要警告安全风险Oracle JRE 8是旧版本除非付费获得长期支持否则不会收到公开的安全更新。在生产环境中使用存在风险。仅用于运行时它只包含java命令不包含javac等开发工具。你的开发环境仍需安装完整的JDK。管理混乱系统中同时存在多个Java版本JDK 11 和 JRE 8容易导致环境变量冲突需要精心管理。5.2 选用特殊的JDK发行版一些第三方OpenJDK发行版为了照顾用户习惯仍然提供了包含jre目录的安装包或生成选项。Adoptium Eclipse Temurin在其历史版本如某些JDK 8构建或特定安装包中可能提供带有JRE的选项。但在其最新的JDK 11版本中通常也遵循了标准的不包含独立JRE的模式。Azul ZuluZulu JDK的某些版本尤其是面向企业的构建可能会提供一种“JDK with JRE”的安装体验或者在安装后提供一个生成JRE的脚本。这是需要到其官网仔细查看发行说明的。Amazon Corretto作为生产就绪的发行版Corretto严格遵循OpenJDK上游标准其JDK安装包不包含独立JRE。操作建议不要将希望完全寄托于此。随着时间推移所有主流的、保持更新的OpenJDK发行版都会向模块化标准看齐。将此方案视为一个临时过渡或针对特定已知版本如某个必须使用的Zulu JDK 11老版本的解决方案。5.3 使用Docker容器隔离环境这是一个非常现代且干净的解决方案尤其适合运维和部署场景。如果宿主机是JDK 11环境而你的某个应用必须在带有传统JRE目录结构的Java 8下运行你可以将其封装在Docker容器中。编写Dockerfile基于一个包含传统JRE的镜像例如openjdk:8-jre注意是jre标签不是jdk。FROM openjdk:8-jre COPY your-app.jar /app/ WORKDIR /app CMD [java, -jar, your-app.jar]构建并运行镜像。在容器内部Java环境是自包含的拥有完整的/usr/lib/jvm/java-8-openjdk-amd64/jre目录结构与宿主机环境完全隔离。这种方法彻底解决了环境冲突问题实现了“一次构建到处运行”是微服务和云原生架构下的最佳实践。6. 深入排查当“解决方法”都失效时如果你尝试了以上所有方法问题依然存在比如配置了符号链接但软件仍报错或者jlink生成的运行时无法启动你的应用那么我们需要进行更深入的排查。这通常意味着问题可能不在Java本身而在其他环节。6.1 检查软件自身的配置或脚本很多时候软件内部有更具体的配置项覆盖了系统环境变量。查找配置文件在软件的安装目录、用户目录如%APPDATA%或~/.config下寻找.ini,.conf,.properties,.xml等格式的配置文件。用文本编辑器打开搜索java.home,jre,javapath等关键词。分析启动脚本找到软件的主启动脚本.bat,.sh,.ps1文件。仔细阅读看它是否直接硬编码了Java路径例如# 错误示例硬编码旧路径 set JAVA_EXE%JAVA_HOME%/jre/bin/java.exe # 正确或应修改为 set JAVA_EXE%JAVA_HOME%/bin/java.exe使用Process MonitorWindows进行动态追踪这是一个高级排查工具。运行Process Monitor设置过滤器路径包含“jre”且进程名为你的软件名。然后启动该软件观察它在文件系统中到底在尝试访问哪些路径。你会清晰地看到它是否在寻找一个不存在的jre/bin/java.exe以及它最终在哪里找到了或没找到可执行文件。这个工具对于诊断任何“文件找不到”的问题都是神器。6.2 检查系统级冲突与权限多个Java版本冲突使用where javaWindows或which -a javaLinux/macOS命令查看系统PATH中所有java命令的位置。排在最前面的就是系统实际使用的Java。确保它是你期望的版本。你可能需要重新调整PATH中目录的顺序或直接使用绝对路径来启动应用。权限问题如果你创建符号链接或安装软件到系统目录如C:\Program Files或/usr/lib请确保操作是在管理员/root权限下进行的并且运行软件的用户有足够的权限读取该目录下的文件。防病毒或安全软件拦截某些安全软件可能会误将新创建的符号链接或jlink生成的可执行文件视为可疑行为而加以阻止。尝试暂时禁用安全软件进行测试。6.3 理解错误信息的本质错误信息“找不到JRE”或“Could not find Java SE Runtime Environment”是一个表象。它的本质是调用方无法在其预期的路径上启动java虚拟机进程。因此我们的所有解决方案都围绕着一个核心确保一个有效的java可执行文件存在于调用方期望的路径上或者通过某种重定向机制让调用方能够访问到它。无论是修正环境变量改变调用方的搜索路径、创建符号链接让旧路径指向新文件还是用jlink生成运行时直接提供旧路径期望的文件结构都是这个核心思想的不同实现。最后分享一个我个人的习惯对于任何需要Java环境的新项目或新服务器我都会在初始化文档中明确写明“本项目要求JDK 11环境变量JAVA_HOME应指向JDK根目录例如/opt/jdk-17PATH中包含$JAVA_HOME/bin。请注意JDK 9版本不包含独立的jre目录所有脚本和配置不应依赖此路径。” 事先约定远胜于事后救火。
返回列表