问题描述 | Bug Description
环境
- HMCL 版本:3.16.3
- 系统:Windows 11 25H2 10.0.26200.9278,x86-64
- 已安装 Java:仅 1 份 —— JDK 25.0.4.1 (BellSoft Liberica)
- 游戏版本:1.20.1 + Forge 整合包
一、核心问题:告警分配完全倒置
这是本报告最重要的一点。当前 HMCL 的 Java 告警分布是反的:
|
AUTO / VERSION |
DETECTED / CUSTOM(手动) |
| 谁做的决策 |
HMCL |
用户 |
| 用户是否知情 |
否 —— 主动交出选择权 |
是 —— 明确指定了路径或版本 |
| 是否有告警 |
❌ 没有 |
✅ 有 |
| 能否自动修正 |
✅ 能(切换/下载 Java) |
❌ 不能(不能覆盖用户显式指定) |
为什么这是错的
选择 AUTO 的用户,恰恰是最不了解、也最不想操心 Java 版本的那批人。"自动"二字的语义就是"由你替我决定"。HMCL 作为决策方,却对这批用户零提示。
反之,手动指定 Java 是有意识的行为——用户知道自己装了什么、选了什么、为什么选。给他们弹窗,是在向唯一已经做出知情决策的人做风险告知。
手动选择本身即隐含前提:用户知晓自己选择的是什么版本。若因此出现问题,那是用户自主选择应承担的风险,而非启动器的责任。
真正需要被提示的,是那些把选择权交给 HMCL、因而无从知晓风险的用户。
这个倒置会自我强化
手动分支唯一有告警的路径,其修复动作恰恰是把用户踢回 AUTO(LauncherHelper.java:544-547):
Controllers.confirm(i18n("launch.advice.java.auto"), ..., () -> {
setting.setJavaAutoSelected(); // ← 弹完窗,切成 AUTO
future.complete(suggestedJava);
}, breakAction);
完整链条:手动指定 → HMCL 告警 → 用户接受建议 → 设置被改为 AUTO → 从此再无告警。
系统主动把用户从"唯一有告警的模式"引导到"唯一没有告警的模式"。
关于 UI 上的版本显示
AUTO 模式下设置页会显示 HMCL 将使用的 Java 版本,但这不构成有效告知:
- 被动信息 ≠ 主动告知。静态显示需要用户主动去读,且不携带"此版本可能有风险"的判断。
- 决策方的义务是在决策发生的时刻主动推送风险,而非把信息摆在角落等待被发现。
- 选了 AUTO 的用户不会去看那个显示——如果愿意看,他早就手动选了。
技术层面的补充
AUTO 是唯一具备自动修正能力的模式。 手动模式下即使告警,HMCL 也只能提示,不能擅自覆盖用户显式指定的路径;而 AUTO 模式下 HMCL 本就拥有完整处置权——可切换到另一个已安装的 Java,或触发下载。
因此告警放在 AUTO 才具备实际价值:既能告知,又能当场给出修正动作(复用 downloadJava)。放在手动模式只能告知、无法修正,是低效的。
二、实测复现(日志证据)
日志关键片段
Java 检测 —— 只有一份 Java:
[01:49:38] Finished Java lookup, found 1
- JDK 25.0.4.1 (x86-64, BellSoft): C:\Program Files\BellSoft\LibericaJDK-25-Full\bin\java.exe
游戏版本(mod 目录):
|-> mods
| |-> alcocraftplus-1.20.1-forge-2.0.2.jar
| |-> architectury-9.2.14-forge.jar
| |-> alexscaves-2.0.1.jar
启动流程 —— checkGameState 同秒完成,无弹窗:
[01:49:48] Launching game version: Closing Song
[01:49:48] Executing task: ...LauncherHelper.checkGameState(LauncherHelper.java:393)
[01:49:48] Task finished: ...LauncherHelper.checkGameState(LauncherHelper.java:393)
[01:49:48] Executing task: ...GameLibrariesTask
[01:49:48] Executing task: ...GameAssetDownloadTask
全程唯一对话框是 TaskExecutorDialogPane(启动进度框),无任何告警弹窗记录。
期望行为
启动前提示:当前 Java 25 高于该环境推荐版本(Java 17),部分 mod 可能不兼容,并给出一键切换/下载 Java 17 的入口。
实际行为
零提示,直接用 Java 25 启动。
精确推演(源码对照,main 分支)
| 约束 |
1.20.1 Forge + Java 25 |
结果 |
VANILLA |
appliesToVersionImpl 要求 version.javaVersion() == null;1.20.1 json 含 javaVersion(17) |
不命中 |
GAME_JSON |
mandatory,atLeast("17") |
Java 25 通过 |
MODDED_JAVA_17 |
between("17","17.999"),isMandatory=false |
违规(suggested 级) |
唯一违规项 MODDED_JAVA_17 属建议级,被 AUTO 分支丢弃(机制见第四节)。
这份日志的意义:1.20.1 Forge 是 HMCL 中上界覆盖最完善的组合(6 次 FORGE 引用、档位齐全)。连它都静默放行,零覆盖的 Fabric / Quilt / NeoForge 只会更糟——它们的 violatedSuggestedConstraints 本身是空集,连"被吞掉"这个动作都不需要发生。
三、检测能力单边:只能可靠检测「Java 太旧」
下界(atLeast)覆盖充分:VANILLA / GAME_JSON / CLEANROOM 从 version.json 推出最低版本,跨加载器通用。
上界覆盖面极窄:
| 能检测"Java 太新"的约束 |
覆盖范围 |
LAUNCH_WRAPPER |
≤1.12.999 且 launchwrapper < 1.13 |
VANILLA_LINUX_JAVA_8 |
Linux x64 + ≤1.12.999 |
MODDED_JAVA_7/8/16/17/21 |
仅 FORGE(且全部 isMandatory=false) |
MODLAUNCHER_8 |
仅 FORGE,1.16.3–1.17.1 特定版本段 |
对各 GameComponentType 的引用计数:
| 类型 |
引用次数 |
FORGE |
6 |
CLEANROOM |
4 |
NEO_FORGE / FABRIC / QUILT / LEGACY_FABRIC / LITELOADER / OPTIFINE |
0 |
FABRIC / NEO_FORGE / QUILT / LEGACY_FABRIC / LITELOADER 在 GameComponentType.java:43-222 均为带 ModLoaderType 的正式枚举常量,无任何约束引用。且 FORGE 与 NEO_FORGE 设计上互斥(GameComponentType.java:96-105,检测到 NeoForge 即 return false),NeoForge 永远不可能命中 MODDED_JAVA_*。
≥1.13 的任何非 Forge 环境(含纯原版)都不存在 Java 上界。
为什么不可忽略
1. HMCL 自身制造了"高版本 Java 是常态"的环境
// Metadata.java:47-49
MINIMUM_REQUIRED_JAVA_VERSION = 17;
MINIMUM_SUPPORTED_JAVA_VERSION = 17;
RECOMMENDED_JAVA_VERSION = 21;
// GameJavaVersion.java:40
LATEST = JAVA_25;
// GameJavaVersion.java:43-44 — MC 26.1+ 直接要求 Java 25
HMCL 要运行就必须有 Java 17+,引导下载的是最新 LTS。用户机器上的 Java 只会被往上推。 "Java 太新"不是边缘场景,而是默认场景。
2. 这是概率性问题,所以正确解法是提醒,不是拦截
1.16.5 + Fabric 用 Java 25 并不崩溃;是否崩溃取决于具体 mod 组合调用了哪些 API,HMCL 无法静态判定。
- ❌ 不应设
isMandatory=true —— 会误伤大量本可正常运行的组合;
- ✅ 必须给出 warning —— 让用户知情并保留"仍要启动"的选择权。
这正是 isMandatory=false 的设计意图。问题在于它既没覆盖到 Fabric / Quilt / NeoForge,也不被 AUTO 分支消费。
四、机制细节:告警为何在 AUTO 下被丢弃
JavaManager.java:355-363 降级时只交出 JavaRuntime,丢掉 violationSuggested:
if (!violationMandatory) {
mandatory = chooseJava(mandatory, java);
if (!violationSuggested)
suggested = chooseJava(suggested, java);
}
}
return suggested != null ? suggested : mandatory; // ← 降级兜底
LauncherHelper.java:445-449 AUTO / VERSION 分支拿到非空结果即放行:
if (java != null) {
return Task.completed(java); // ← 零校验、零提示
}
而 DETECTED / CUSTOM 分支(main 分支 LauncherHelper.java:517-726)有完整的 suggestions 生成与弹窗(630-723 行),整段只存在于 else 分支。
影响矩阵(机器上只有 Java 25)
| 场景 |
检测到违规? |
AUTO |
手动指定 |
| 1.12.2- + Forge(LaunchWrapper) |
mandatory 违规 |
✅ 弹窗 |
✅ 弹窗 |
| 1.20.1 + Forge(本日志实测) |
suggested 违规 |
❌ 静默 |
⚠️ 弹窗 |
| 1.16.5 + Fabric |
零违规 |
❌ 静默 |
❌ 无弹窗 |
| 1.20.4 + Fabric / Quilt |
零违规 |
❌ 静默 |
❌ 无弹窗 |
| 1.20.2–1.20.4 + NeoForge |
零违规 |
❌ 静默 |
❌ 无弹窗 |
| 1.16.5 原版 |
零违规 |
❌ 静默 |
❌ 无弹窗 |
仅修复"AUTO 不消费告警"不够:Fabric / Quilt / NeoForge 的 violatedSuggestedConstraints 是空集,手动指定也不会提示。
补充:GameSettings.java:966+ 的 DETECTED 模式在 pathHash 匹配失败后也会 fallback 到 findSuitableJava(),同样静默降级,需一并覆盖。
启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File
这里采用了手动禁用适合版本java的方法进行测试
2026-09-01T01-49-37.log
问题描述 | Bug Description
环境
一、核心问题:告警分配完全倒置
这是本报告最重要的一点。当前 HMCL 的 Java 告警分布是反的:
为什么这是错的
选择 AUTO 的用户,恰恰是最不了解、也最不想操心 Java 版本的那批人。"自动"二字的语义就是"由你替我决定"。HMCL 作为决策方,却对这批用户零提示。
反之,手动指定 Java 是有意识的行为——用户知道自己装了什么、选了什么、为什么选。给他们弹窗,是在向唯一已经做出知情决策的人做风险告知。
这个倒置会自我强化
手动分支唯一有告警的路径,其修复动作恰恰是把用户踢回 AUTO(
LauncherHelper.java:544-547):完整链条:手动指定 → HMCL 告警 → 用户接受建议 → 设置被改为 AUTO → 从此再无告警。
系统主动把用户从"唯一有告警的模式"引导到"唯一没有告警的模式"。
关于 UI 上的版本显示
AUTO 模式下设置页会显示 HMCL 将使用的 Java 版本,但这不构成有效告知:
技术层面的补充
AUTO 是唯一具备自动修正能力的模式。 手动模式下即使告警,HMCL 也只能提示,不能擅自覆盖用户显式指定的路径;而 AUTO 模式下 HMCL 本就拥有完整处置权——可切换到另一个已安装的 Java,或触发下载。
因此告警放在 AUTO 才具备实际价值:既能告知,又能当场给出修正动作(复用
downloadJava)。放在手动模式只能告知、无法修正,是低效的。二、实测复现(日志证据)
日志关键片段
Java 检测 —— 只有一份 Java:
游戏版本(mod 目录):
启动流程 ——
checkGameState同秒完成,无弹窗:全程唯一对话框是
TaskExecutorDialogPane(启动进度框),无任何告警弹窗记录。期望行为
启动前提示:当前 Java 25 高于该环境推荐版本(Java 17),部分 mod 可能不兼容,并给出一键切换/下载 Java 17 的入口。
实际行为
零提示,直接用 Java 25 启动。
精确推演(源码对照,main 分支)
VANILLAappliesToVersionImpl要求version.javaVersion() == null;1.20.1 json 含 javaVersion(17)GAME_JSONatLeast("17")MODDED_JAVA_17between("17","17.999"),isMandatory=false唯一违规项
MODDED_JAVA_17属建议级,被 AUTO 分支丢弃(机制见第四节)。三、检测能力单边:只能可靠检测「Java 太旧」
下界(
atLeast)覆盖充分:VANILLA/GAME_JSON/CLEANROOM从 version.json 推出最低版本,跨加载器通用。上界覆盖面极窄:
LAUNCH_WRAPPERVANILLA_LINUX_JAVA_8MODDED_JAVA_7/8/16/17/21isMandatory=false)MODLAUNCHER_8对各
GameComponentType的引用计数:FORGECLEANROOMNEO_FORGE/FABRIC/QUILT/LEGACY_FABRIC/LITELOADER/OPTIFINEFABRIC/NEO_FORGE/QUILT/LEGACY_FABRIC/LITELOADER在GameComponentType.java:43-222均为带ModLoaderType的正式枚举常量,无任何约束引用。且FORGE与NEO_FORGE设计上互斥(GameComponentType.java:96-105,检测到 NeoForge 即return false),NeoForge 永远不可能命中MODDED_JAVA_*。≥1.13 的任何非 Forge 环境(含纯原版)都不存在 Java 上界。
为什么不可忽略
1. HMCL 自身制造了"高版本 Java 是常态"的环境
HMCL 要运行就必须有 Java 17+,引导下载的是最新 LTS。用户机器上的 Java 只会被往上推。 "Java 太新"不是边缘场景,而是默认场景。
2. 这是概率性问题,所以正确解法是提醒,不是拦截
1.16.5 + Fabric 用 Java 25 并不崩溃;是否崩溃取决于具体 mod 组合调用了哪些 API,HMCL 无法静态判定。
isMandatory=true—— 会误伤大量本可正常运行的组合;这正是
isMandatory=false的设计意图。问题在于它既没覆盖到 Fabric / Quilt / NeoForge,也不被 AUTO 分支消费。四、机制细节:告警为何在 AUTO 下被丢弃
JavaManager.java:355-363降级时只交出JavaRuntime,丢掉violationSuggested:LauncherHelper.java:445-449AUTO / VERSION 分支拿到非空结果即放行:而
DETECTED/CUSTOM分支(main 分支LauncherHelper.java:517-726)有完整的 suggestions 生成与弹窗(630-723 行),整段只存在于 else 分支。影响矩阵(机器上只有 Java 25)
仅修复"AUTO 不消费告警"不够:Fabric / Quilt / NeoForge 的
violatedSuggestedConstraints是空集,手动指定也不会提示。启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File
这里采用了手动禁用适合版本java的方法进行测试
2026-09-01T01-49-37.log