简介:JDK 1.7.0_13 是 Java 7 系列的一个完整开发工具包,面向仍需维护 Java 7 遗留项目、搭建基础 Java 开发环境或学习 JDK 目录结构的开发者。它集成 javac 编译器、Java 虚拟机与标准类库,并附带 javadoc、jdb、jconsole、jmap、jhat 等诊断与文档工具,可用于源码编译、程序运行、性能观察和内存分析。压缩包共 1240 个文件,约 79.07MB,以 jar 类库、xml 配置、dll 与 exe 可执行组件、properties 属性文件为主,同时包含少量 bat 脚本、txt 说明及国际化时区数据,目录层次与官方 JDK 安装结构基本一致。已有 373 人学习下载。对于需要复现旧版编译行为、排查版本兼容问题或对照 JRE/JDK 组成的学习者,这份包能提供较完整的运行与调试组件,也便于理解 Java 工具链各部分的实际用途与组织方式。它适合在离线环境中建立 Java 7 编译与运行基线,减少自行搜集组件的成本,对教学演示和旧项目迁移前的环境核验也有参考意义。
1. 为什么还有人在装 JDK 1.7.0_13:被 8 和 17 挤到角落的 java 开发环境
一套跑在内网的老业务系统,换了三拨人维护,构建脚本里写死着JAVA_HOME=/opt/jdk1.7.0_13,换任何新版本 JDK 编译出来的 class 一上线就抛UnsupportedClassVersionError。这就是 JDK 1.7.0_13 今天的真实处境:它不是拿来学新特性的 java 开发环境,而是一份「历史遗留运行时」——你要复现十年前的构建结果、要连接只认老加密套件的中间件、要维护一套不敢动的老代码,就得把它原样装回去。这份资源适合三类人:接手遗留系统的维护者、需要复刻历史构建产物的测试与发布岗、以及要研究 Java 7 时期运行时行为的同学。不适合新项目起步,这一点先说死。
2. 解压之后先看清目录:jdk1.7.0_13 的结构、自检与最小验证
拿到一个安装包,最危险的动作就是双击下一步。JDK 有两种常见分发形态,选错了后面全是玄学。
2.1 压缩包版与安装器版的差别,为什么我更倾向压缩包
安装器版(Windows 上是 exe,Linux 上是 rpm 之类)会往系统里写注册表项、往公共目录塞文件、有可能自动往 PATH 里追加一段路径,还可能把java.exe复制到系统目录。这类副作用在一台只装一个 JDK 的机器上无所谓,但在需要和多版本共存、需要随时回滚的机器上就是血泪经验:你以为是 PATH 顺序问题,实际是系统目录里那个 java 抢先命中了。
压缩包版(tar.gz 或 zip)解压出来就是一个自包含目录,删掉目录等于卸载干净,想同时放 1.7 和别的版本互不干扰也很容易。我一般会把所有 JDK 统一放在一个不带空格、不带中文的路径下,比如 Linux 用/opt/,Windows 用C:\jdk\。
# 解压到统一目录,路径不要出现空格和中文 tar -zxf jdk1.7.0_13.tar.gz -C /opt/ # 确认解压出来的顶层目录名 ls -d /opt/jdk1.7.0_13参数说明:-C指定解压目标目录,别省略,否则包会散在当前工作目录;如果压缩包内层还套了一层目录,解压后你会得到/opt/xxx/jdk1.7.0_13,这时候要么用--strip-components剥掉一层,要么直接把内层目录挪出来,别让JAVA_HOME指向一个带多余层级的路径。
目录结构大致是这样:
jdk1.7.0_13/ ├── bin/ # java、javac、jar、jps、jstack 等可执行文件 ├── jre/ # 自带的运行时,含 bin/java 与 lib/rt.jar ├── lib/ # tools.jar、dt.jar 等开发期依赖 ├── include/ # JNI 头文件 ├── db/ # 内置的 Java DB 数据库 ├── src.zip # 标准库源码 └── COPYRIGHT 等许可文件jre目录是关键:JDK 自带的这套运行时,和系统里另外装的一套 JRE 完全无关。很多人JAVA_HOME指对了,但跑起来用的是别处的 JRE,问题就出在这里。
2.2 装完先跑四条命令,确认版本唯一
不要相信任何「应该装好了」的判断,用命令说话。
# 1. 执行文件本身能跑,且能报出版本 /opt/jdk1.7.0_13/bin/java -version # 2. 编译器版本必须与运行时一致 /opt/jdk1.7.0_13/bin/javac -version # 3. 当前 shell 实际命中的 java 是哪一个 which java # Linux/macOS where java # Windows cmd # 4. 确认 JAVA_HOME 指到了 JDK 根目录,而不是 jre 或 bin echo $JAVA_HOME这里有个细节值得单拎出来:java -version的输出走的是标准错误流,不是标准输出。你在脚本里写java -version | grep version会拿到空结果,正确写法是java -version 2>&1 | grep version。这个坑每年都有人踩。
版本号至少要对齐三处:java -version报的版本、javac -version报的版本、JAVA_HOME指向目录名里的版本。三者只要有一处不一致,就说明环境里存在第二份运行时,后面编译能过、运行报错,或者反过来,排查起来很磨人。
2.3 bin 目录里哪些工具值得先摸一遍
bin下面工具不少,但真正常用的就那几个。java和javac不必说;jar用来打包,后面会细讲;jps列出当前用户启动的所有 Java 进程及主类名,比ps好用到不是一个量级;jstack抓线程栈,卡死时救命;jmap看堆与生成转储;jstat看 GC 频率;keytool管证书,处理握手问题时绕不开。
lib下的tools.jar是编译器与 attach 机制的实现所在,某些构建工具和诊断工具会显式引用它;rt.jar是运行时类库本体。这两份 jar 一旦被别的版本覆盖,会出现非常诡异的行为,所以多版本共存时不要在全局 CLASSPATH 里写死它们。
3. JAVA_HOME 指错一层,后面全盘皆输:多版本共存下的切换方案
环境变量这件事,配一次管三年,配错了查三天。先把职责讲清楚,再给切换方案。
3.1 JAVA_HOME、PATH、CLASSPATH 的职责边界
三者的分工很明确,但混用的人特别多。
| 变量 | 该指哪儿 | 作用 | 常见错误 |
|---|---|---|---|
| JAVA_HOME | JDK 根目录.../jdk1.7.0_13 | 各类构建工具、启动脚本靠它定位 JDK | 指到bin或jre子目录 |
| PATH | %JAVA_HOME%\bin或$JAVA_HOME/bin | 让 shell 能直接敲java、javac | 把系统目录里的 java 排在前面 |
| CLASSPATH | 一般留空 | 全局类搜索路径 | 写死后覆盖了-cp,导致依赖错乱 |
JAVA_HOME指向 JDK 根目录是硬约束。构建工具拿到这个路径之后,会自己拼bin/javac、lib/tools.jar、jre/lib/rt.jar,你指到bin下面,它拼出来的就是bin/bin/javac,直接找不到文件。
CLASSPATH在现代用法里应该留空,依赖全部通过-cp或构建工具管理。设了全局 CLASSPATH 最典型的翻车现象是:你明明用-cp指定了正确的路径,程序却加载到了另一个版本的 jar,因为全局 CLASSPATH 的优先级和追加顺序会干扰。
PATH的顺序是隐藏杀手。Windows 上安装器版可能把 java 放进系统目录,Linux 上系统包管理器可能在/usr/bin/java放了软链。只要这些位置排在$JAVA_HOME/bin之前,你敲的java就不是你以为的那个。所以切换环境时,把JAVA_HOME/bin前置到 PATH 开头,而不是追加到末尾。
3.2 Windows:一个 bat 脚本解决频繁切换
手点「系统属性 → 环境变量」是最低效的方式,还容易改错。我习惯在C:\jdk\下放一个切换脚本,需要哪套就调哪套,只影响当前命令行窗口。
@echo off REM switch-jdk7.bat:只对当前 cmd 窗口生效,不污染系统环境 set JAVA_HOME=C:\jdk\jdk1.7.0_13 set PATH=%JAVA_HOME%\bin;%PATH% set CLASSPATH= REM 立刻验证,避免脚本写错了还不知道 java -version 2>&1 javac -version 2>&1 echo JAVA_HOME=%JAVA_HOME%逻辑说明:set在批处理里作用域是当前进程,所以关掉窗口就恢复,比setx安全得多。set CLASSPATH=这一行是显式清空,防止上一位维护者留下的全局值在里面捣鬼。最后三行验证千万别省,尤其java -version后面那个2>&1——不加重定向,版本信息根本不会显示。
如果确实需要写进用户级环境变量,才用setx,但要清楚它的限制:值超过一定长度会被截断,且它不会影响已经打开的窗口。截断这件事在 PATH 很长的时候特别隐蔽,表现为部分命令忽然找不到。
3.3 Linux:alternatives 与 shell 函数两条路
服务器上多人共用一个账号池的时候,我倾向于用update-alternatives做系统级注册,好处是切换有记录、可回滚。
# 注册两套 JDK,最后的数字是优先级,越大越优先 update-alternatives --install /usr/bin/java java /opt/jdk1.7.0_13/bin/java 100 update-alternatives --install /usr/bin/javac javac /opt/jdk1.7.0_13/bin/javac 100 # 交互式切换 update-alternatives --config java update-alternatives --config javac注意java和javac是两条独立的 alternatives 链,只切其中一个会得到「编译器是新版、运行时是老版」的分裂状态,编译能过运行报UnsupportedClassVersionError就是这么来的。两个都要切。
如果只是自己临时用,写个 shell 函数更轻:
# 放进 ~/.bashrc,之后敲 jdk 7 就切过去 jdk() { case "$1" in 7) export JAVA_HOME=/opt/jdk1.7.0_13 ;; *) echo "用法: jdk 7"; return 1 ;; esac export PATH="$JAVA_HOME/bin:$PATH" export CLASSPATH= java -version 2>&1 | head -n 1 }这里PATH用的是前置拼接$JAVA_HOME/bin:$PATH,保证新路径优先命中;如果用:$JAVA_HOME/bin追加,前面已有的 java 还是会被先找到,切了等于没切。
3.4 三处对齐才算真的切成功
切完之后别急着跑业务,用一条串联命令确认:
echo "JAVA_HOME=$JAVA_HOME" which java java -version 2>&1 | head -n 1 javac -version 2>&1四条输出要对得上:JAVA_HOME是jdk1.7.0_13的根目录,which java落在$JAVA_HOME/bin/java,两条版本号都是 1.7.0_13。任何一条对不上,说明环境里还有残留,先解决再往下走。这套检查我后来写成了脚本,最后一章会给出来。
提示:切换环境后,已经启动的进程不会跟着变。改了
JAVA_HOME却觉得「没生效」,先确认你是不是在旧进程里看结果。
4. javac 与 java 的参数账:-source 1.7 管不住的东西
编译能过、运行报错,或者编译出来的包在别的机器上跑不起来,八成是参数没交代清楚。这一章把编译期和运行期的关键参数摊开讲。
4.1 编译三件套:-source、-target、-encoding
# 标准编译命令:源码目录 src,输出目录 out javac -source 1.7 -target 1.7 -encoding UTF-8 \ -d out \ src/Demo.java三个参数各管一件事:
-source 1.7:按 Java 7 的语言规范做语法检查。用了 lambda、try-with-resources 之外的 Java 8 语法会直接编译失败。-target 1.7:生成 class 文件的字节码版本号为 51。这个数字决定了它能被哪个版本的 JVM 加载。-encoding UTF-8:告诉编译器源文件是什么字符集。不指定的话,编译器用平台默认编码读文件——中文 Windows 上默认是 GBK,UTF-8 保存的源码里有中文注释或中文字符串,就会出现乱码甚至「非法字符」编译错误。
执行时通常会看到一条警告:bootstrap class path not set in conjunction with -source 1.7。这条警告的含义是:你只限制了语法和字节码版本,但没有限制可用的标准库 API。也就是说,如果编译时用的 JDK 本身比 1.7 新,你完全可能写出调用了高版本 API 的代码,编译通过、字节码是 51,但放到 1.7 的运行时上就抛NoSuchMethodError。真正的隔离要靠-bootclasspath指向 1.7 的rt.jar:
javac -source 1.7 -target 1.7 -encoding UTF-8 \ -bootclasspath /opt/jdk1.7.0_13/jre/lib/rt.jar \ -d out src/Demo.java只加-source/-target不加-bootclasspath,是「看起来兼容、实际不兼容」的典型来源。
4.2 运行参数:堆、PermGen 和 GC 选择
Java 7 的内存模型和 Java 8 之后有一个根本区别:永久代(PermGen)还在,类元数据、字符串常量池、静态变量都放在这块独立区域里,它不在-Xmx的管理范围内。所以只调-Xmx是治不了PermGen space的。
java -Xms256m -Xmx512m \ -XX:PermSize=128m -XX:MaxPermSize=256m \ -XX:+UseConcMarkSweepGC \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/var/log/java/ \ -cp out Demo| 参数 | 含义 | 设置建议 |
|---|---|---|
-Xms | 初始堆大小 | 和生产-Xmx设成一样,避免运行期扩容抖动 |
-Xmx | 最大堆大小 | 按容器/物理机内存的 50%~70% 给,留出堆外与系统开销 |
-XX:PermSize | 永久代初始大小 | 一般 64m~128m 起 |
-XX:MaxPermSize | 永久代上限 | 有大量动态类生成时给到 256m 以上 |
-XX:+UseConcMarkSweepGC | 启用 CMS 收集器 | 老年代停顿敏感的服务常用 |
-XX:+HeapDumpOnOutOfMemoryError | OOM 时自动转储 | 生产必开,事后排查的唯一现场 |
GC 选择上要有个预期:1.7.0_13 这个版本里 G1 虽然可以打开,但成熟度远不如后来的版本,生产上不建议拿它冒险,CMS 或并行收集器是更稳的选择。另外,-Xms和-Xmx设成不等,在容器化或内存受限的环境里会表现为「启动正常、跑一段开始频繁 GC」,这是堆反复扩容收缩造成的,不是代码问题。
4.3 jar 打包与清单文件里的那个空行
# 先准备清单文件 manifest.txt # 内容只有一行:Main-Class: Demo jar cfm app.jar manifest.txt -C out .参数说明:c创建、f指定输出文件名、m指定清单文件。-C out .表示切到out目录再把当前内容打进去,这样 jar 内部的路径才是Demo.class而不是out/Demo.class——后者会导致ClassNotFoundException。
清单文件有一条规定很容易踩:文件最后必须有一个空行,否则最后一行属性可能不被解析。表现是java -jar app.jar报no main manifest attribute,你打开 MANIFEST.MF 看明明有Main-Class,就是找不到原因。加个换行就好了。
java -jar还有个副作用值得知道:它会忽略-cp,类路径完全由 jar 清单里的Class-Path决定。所以带外部依赖时,要么把依赖也打进 jar,要么在清单里逐条列相对路径,别指望命令行-cp能补上。
4.4 一段最小代码验证运行时行为
环境装完,用一段代码同时验证版本、编码和堆上限,比看文档靠谱。
public class Demo { public static void main(String[] args) { // 运行时版本,确认加载的是 1.7.0_13 System.out.println("java.version = " + System.getProperty("java.version")); // 默认字符集,中文乱码问题先看这两个值 System.out.println("file.encoding = " + System.getProperty("file.encoding")); System.out.println("sun.jnu.encoding = " + System.getProperty("sun.jnu.encoding")); // 堆上限,验证 -Xmx 是否真的生效 long max = Runtime.getRuntime().maxMemory() / 1024 / 1024; System.out.println("maxMemoryMB = " + max); // 中文输出测试,观察控制台是否乱码 System.out.println("中文输出测试"); } }javac -source 1.7 -target 1.7 -encoding UTF-8 -d out src/Demo.java java -Xmx512m -cp out Demo跑出来如果maxMemoryMB明显小于你设的-Xmx,说明有别的参数覆盖了它——常见于启动脚本里已经写死了JAVA_OPTS,你在命令行追加的反而排在后面被前值覆盖。file.encoding在中文 Windows 上通常是 GBK,在 Linux 上取决于LANG,这个值直接决定new String(bytes)和文件读写的结果,后面避坑章会展开。
5. 五个翻车现场:从 UnsupportedClassVersionError 到握手失败
环境类问题难排查,难在现象和根因隔得远。这一章按「现象 → 原因 → 解决」给五条,都是我实际碰到的。
5.1 排查顺序:先对齐版本,再看异常栈
动手之前先固定一个顺序,能省掉一半时间。第一步,确认java -version和javac -version是否一致,排除运行时选错;第二步,看异常栈最顶上的异常类型,UnsupportedClassVersionError属于版本问题,NoSuchMethodError属于 API 不匹配,OutOfMemoryError: PermGen space属于内存配置问题,三类根因完全不同;第三步,看jps -l确认你操作的进程确实是那个进程——多实例部署时连错端口、改错进程是高频事故。
顺手可以开两个观察点:jstat -gcutil <pid> 1000每秒打一次 GC 与各区占用,jstack <pid>卡住的时候抓一份线程栈。这两个命令在 1.7 的bin下都有,不需要额外装工具。
5.2 坑一:UnsupportedClassVersionError
现象:应用启动直接失败,异常信息里有Unsupported major.minor version 52.0。
原因:class 文件的字节码版本号高于当前 JVM 能加载的上限。51 对应 Java 7,52 对应 Java 8。用新版编译器编出来的包,扔到 1.7 的运行时上跑,必然报这个。另一种情况是编译机上有两个javac,构建脚本命中的是新版。
解决:先确认构建产物到底是谁编的,用javap -v Demo.class | grep major看版本号。要产出能跑在 1.7 上的包,编译机必须指到 1.7 的javac,并加上-source 1.7 -target 1.7。但要注意前面说过的:光加这两个参数只改字节码版本,不改可用 API,还得配-bootclasspath指向 1.7 的rt.jar,否则会出现「版本对了、方法没了」的NoSuchMethodError。
5.3 坑二:OutOfMemoryError: PermGen space
现象:应用跑一段时间后崩溃,日志开头是java.lang.OutOfMemoryError: PermGen space,堆内存看着还很空。
原因:永久代溢出。1.7 的类元数据、常量池、动态生成的类都在 PermGen 里,它不受-Xmx管辖。频繁热部署、大量动态代理、JSP 反复编译,都会持续往这块塞类,默认的几十兆很快就不够。
解决:加-XX:MaxPermSize=256m(或更高)并重启,同时观察是否仍在增长。如果加了还在涨,那不是配置问题,是类加载器泄漏——典型场景是每次热部署都新建一个类加载器却没人释放旧的,老类永远回收不掉。这时候jmap -permstat <pid>能看到每个类加载器占了多少,定位到具体的重复加载来源。
注意:这个参数在 Java 8 之后已经失效并被忽略,别把同一套启动脚本原样搬到新版本上,会得到「参数不认识」的警告甚至启动失败。
5.4 坑三:中文乱码,编译期和运行期是两回事
现象:源码里中文注释编译报「非法字符」,或者程序里输出的中文变成????,或者读进来的配置文件中文字段全乱。
原因:三个编码点各自独立。编译期由-encoding决定怎么读源文件;运行期的file.encoding决定字符串与字节之间怎么转;sun.jnu.encoding决定文件名的编码。三者不一致就会出现一部分正常、一部分乱码的割裂现象。
解决:编译固定加-encoding UTF-8;运行加-Dfile.encoding=UTF-8;Linux 上同时确认LANG是 UTF-8 而不是C。读取外部文件时永远显式指定字符集,别用无参的new InputStreamReader(is),那个用的是平台默认值,换台机器就变。
java -Dfile.encoding=UTF-8 -cp out Demo5.5 坑四:HTTPS 握手失败,服务端只认新版协议
现象:调用外部 HTTPS 接口时报javax.net.ssl.SSLHandshakeException,本地用别的工具测同一个地址是通的。
原因:对端把协议版本下限提到了 TLS 1.2,而这个运行时默认协商出来的版本偏低,直接握手失败。另一个常见原因是证书链换成了该版本不认识的签名算法或密钥类型,验证阶段就断。
解决:先试着显式指定协议:
java -Dhttps.protocols=TLSv1.2 -cp out Demo如果加上之后仍然失败,用-Djavax.net.debug=ssl:handshake打开握手日志,看卡在协议协商还是证书校验。若是证书算法不被支持,这个版本本身没有补丁可打,只能换运行时,或者让对端保留一个兼容端点。这类问题不要指望改客户端代码绕过去,握手发生在应用逻辑之前。
5.6 坑五:路径里的空格和中文,以及被截断的 PATH
现象:javac报「找不到文件」,路径明明存在;或者某个命令昨天能用今天忽然「不是内部或外部命令」。
原因:路径含空格时,脚本里没加引号会被拆成多段;路径含中文时,某些工具的字符集处理会出问题。而 PATH「忽然失效」多半是长度超限被截断——Windows 上用户级变量写入对长度有限制,超长部分会被悄悄砍掉,砍掉的正好是末尾追加的那一段。
解决:JDK 安装路径、工程路径、输出路径一律用纯英文无空格,这是最省事的一条。脚本里引用变量统一加引号:"%JAVA_HOME%\bin\javac"。PATH 别无限追加,定期清理,把 JDK 放在靠前位置而不是末尾,既避免截断也避免被别的版本抢先命中。
6. 把运行时锁进脚本:一份可复用的环境自检与版本矩阵
靠记忆切换环境,迟早出事。我的习惯是把判断逻辑写成脚本,每次进现场先跑一遍,让机器告诉我当前是什么状态。
6.1 一个自检脚本,把版本、编码、堆一次性打出来
下面这段放在构建脚本文本的最前面,任何一项不符就直接退出,避免带着错误环境跑完整个流水线。
import os import re import subprocess import sys JDK_HOME = "/opt/jdk1.7.0_13" EXPECT_VERSION = "1.7.0_13" def run(cmd): # java -version 输出走标准错误流,必须合并后再解析 p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) return p.stdout.strip() def check(): errors = [] # 1. JAVA_HOME 必须指向 JDK 根目录,且目录真实存在 if os.environ.get("JAVA_HOME") != JDK_HOME: errors.append(f"JAVA_HOME 应为 {JDK_HOME},实际 {os.environ.get('JAVA_HOME')}") # 2. java 与 javac 版本必须一致 java_out = run([os.path.join(JDK_HOME, "bin", "java"), "-version"]) javac_out = run([os.path.join(JDK_HOME, "bin", "javac"), "-version"]) if EXPECT_VERSION not in java_out: errors.append(f"java 版本不符: {java_out.splitlines()[0]}") if EXPECT_VERSION not in javac_out: errors.append(f"javac 版本不符: {javac_out}") # 3. PATH 里第一个 java 必须落在 JDK_HOME 内,防止被系统目录抢占 first_java = subprocess.run(["which", "java"], stdout=subprocess.PIPE, text=True).stdout.strip() if not first_java.startswith(JDK_HOME): errors.append(f"PATH 命中的 java 是 {first_java}") return errors if __name__ == "__main__": problems = check() if problems: for p in problems: print("[FAIL]", p) sys.exit(1) print("[OK] 运行环境校验通过")逻辑说明:run里把 stderr 合进 stdout,是因为版本信息在错误流里;直接用绝对路径调用bin/java而不是裸命令,排除 PATH 干扰;第三步单独用which反查,是因为「绝对路径没问题」不代表「用户敲 java 时命中的就是它」,这两件事经常分裂。脚本返回非零退出码,流水线里可以直接&&串联。
6.2 版本矩阵:什么时候该留住 1.7.0_13,什么时候必须走
| 场景 | 建议 | 理由 |
|---|---|---|
| 复现历史构建、对齐存量制品 | 保留 1.7.0_13 | 字节码与依赖版本必须严格一致 |
| 维护无法改动的遗留系统 | 保留,但隔离部署 | 升级风险高于收益,别顺手升 |
| 新功能开发 | 不要用 | 缺少长期安全更新与生态支持 |
| 需要连接强制新协议的对外接口 | 评估升级路径 | 客户端侧难有补救空间 |
| 只在本地做代码考古与教学 | 用压缩包版临时起 | 干净、可随时删 |
矩阵背后是一条朴素的判断:这个运行时的价值在于「保持不变」,一旦开始为它打补丁、加兼容层,成本会迅速超过升级本身。所以它在环境里应该是一个被明确隔离、被脚本锁定、被文档标注的组件,而不是一个随手export一下就完事的默认项。
吃过一次亏之后,我每次接手新环境的第一件事就是把上面那段自检脚本跑一遍,输出贴进交接文档,谁也不许跳过。那天的问题其实很简单:构建机上的JAVA_HOME指对了,但有人往 PATH 末尾追加了一个系统目录,java一直命中的是另一个版本,编译产物没问题、启动脚本没问题,只有跑起来才报错,查了整整一个下午。从那以后我每次切环境都强制走一遍版本对齐检查,java、javac、JAVA_HOME三处对不上就不往下走。希望帮到你。
本文还有配套的精品资源,点击获取