QUICKREF · MIGRATION · 8 / 11 → 17
从 8 / 11 迁移到 17
JDK 17 · 速查主题 06 / 06 · 预计 25 分钟
一句话升级到 17 的主要成本不在新语法,而在旧假设:被移除的 RMI Activation、失效的 --illegal-access、对 JDK 内部 API 的反射访问,以及只在 21 才改变的 Security Manager 行为。
学完你要能自己回答
- 从 8 / 11 升到 17,哪些移除项和弃用项必须先处理?
- --illegal-access=permit 在 17 还能用吗?InaccessibleObjectException 应该怎么定位?
- Security Manager 在 17 是什么状态?为什么不能照搬 21 的结论?
- 升级到 17 的验证清单应该包含哪些命令和参数?
先确认版本坐标01
JDK 17 是 LTS,class 文件版本 61,默认 G1。升级前先把源码、编译工具、运行时和依赖的版本对齐;class 文件版本不匹配通常表现为启动即失败。
$ java -version
openjdk version "17.0.13" 2024-10-15 LTS
OpenJDK Runtime Environment Corretto-17.0.13.11.1 (build 17.0.13+11-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.13.11.1 (build 17.0.13+11-LTS, mixed mode, sharing)
| 版本 | class 文件 | 默认 GC | 迁移时最值得注意的变化 |
|---|---|---|---|
| JDK 8 | 52 | Parallel | PermGen 已移除;Java EE 模块仍在 |
| JDK 11 | 55 | G1 | Java EE / CORBA 移除;Nashorn 弃用 |
| JDK 17 | 61 | G1 | 强封装 JDK 内部;RMI Activation 移除;Security Manager 弃用 |
| JDK 21 | 65 | G1 | switch 模式匹配正式;Security Manager 默认禁用;分代 ZGC |
用 --release 17 编译,而不是只改 -source / -target。--release 会同时约束语法、字节码版本和可用的标准库 API 快照,避免编译到 18+ 才有的 API。
升级顺序通常是 8 → 11 → 17,每个 LTS 作为检查点。直接 8 → 17 不是不可能,但会把多个版本的移除项和依赖升级叠在一起,定位成本更高。
移除与弃用:先分清「现在就不能用」和「以后不能用」02
| 项目 | JDK 17 状态 | 处理方式 |
|---|---|---|
| RMI Activation(java.rmi.activation、rmid) | 已移除(JEP 407) | 删除相关代码与启动脚本;java.rmi 本身仍在 |
| --illegal-access 选项 | 已移除(JEP 403) | 传入只会警告并被忽略;改用 --add-opens |
| CMS GC | 14 已移除 | -XX:+UseConcMarkSweepGC 让 JVM 无法启动 |
| PermGen 参数 | 8 已移除 | -XX:MaxPermSize 让 JVM 无法启动 |
| AOT / Graal JIT(jaotc) | 已移除(JEP 410) | 删除相关工具链与参数 |
| Security Manager | 17 弃用(JEP 411),仍可安装 | 17 默认允许并打印警告;21 默认禁用;24 永久禁用 |
| Applet API | 17 弃用待移除(JEP 398),类仍在 | 迁移到其他 UI 技术;不要新增依赖 |
| Thread.stop / suspend / resume | 仍存在但长期弃用 | 17 仍可调用;21 起抛 UnsupportedOperationException |
RMI Activation 的移除只影响 java.rmi.activation 和 rmid,普通 RMI(java.rmi)仍在。升级时先用 jdeps / 编译错误找出引用,再删启动脚本里的 rmid。
$ java --list-modules | grep rmi
java.management.rmi@17.0.13
java.rmi@17.0.13
jdk.naming.rmi@17.0.13
$ ls $JAVA_HOME/bin | grep rmid
# 无输出:rmid 已随 JEP 407 移除
Security Manager 是最容易照搬错结论的一项:17 只是弃用,默认仍能安装(有警告);21 才默认禁用。把 21 的「抛 UOE」直接写进 17 的迁移文档,会误导读者。
强封装与反射:先升级库,再加参数03
JDK 16 的 JEP 396 把默认策略改为拒绝,但仍可用 --illegal-access=permit 放行;JDK 17 的 JEP 403 移除了这个开关,JDK 内部包从此一律强封装。
import java.lang.reflect.Field;
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
type=InaccessibleObjectException msg=Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @...
报错要这样读:被访问的成员在 java.base 模块的 java.lang 包里;该包没有向调用方模块 opens。classpath 上的代码属于未命名模块,所以目标通常写 ALL-UNNAMED。
# 临时放行:把 java.base/java.lang 打开给 classpath 代码
java --add-opens java.base/java.lang=ALL-UNNAMED -cp out ReflectDemo
# opened=true
# 17 上这个选项只会被忽略
$ java --illegal-access=permit -cp out ReflectDemo
OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=permit; support was removed in 17.0
type=InaccessibleObjectException
msg=Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @...
--add-opens是运行时参数,只影响反射的深度访问,不改变编译期可见性。--add-exports影响编译期与运行期的静态访问:让未导出的 public 类型可见。- 每加一个参数都要记录原因、作用模块和移除计划;它们会掩盖真正的升级问题。
sun.misc.Unsafe等少数内部 API 仍通过jdk.unsupported导出,但这是兼容通道,不是鼓励使用。
处理顺序永远是:升级依赖 → 替换内部 API → 最后才用最小范围的 --add-opens。全局打开 java.base/java.lang 会让未来的升级更难,也削弱了封装保护。
工具与参数:把问题在启动前找出来04
jdeps 找静态引用 JDK 内部 API 的类,jdeprscan 找使用了已弃用 / 待移除 API 的类;两者互补,都不能发现纯反射访问。
# 找出依赖 JDK 内部 API 的类
jdeps --jdk-internals -R build/libs/app.jar
# 找出使用了「已弃用 / 将被删除」API 的类
jdeprscan --release 17 --for-removal build/libs/app.jar
# 编译时把 removal 警告暴露出来
javac --release 17 -Xlint:removal -d out $(find src -name "*.java")
$ jdeps -J-Duser.language=en --jdk-internals out/ExportDemo.class
ExportDemo.class -> java.base
ExportDemo -> sun.nio.ch.DirectBuffer JDK internal API (java.base)
Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependence on any JDK internal APIs.
| 旧参数 | JDK 17 行为 | 处理方式 |
|---|---|---|
-XX:+UseConcMarkSweepGC | Unrecognized VM option,JVM 无法启动 | 删除;CMS 在 14 已移除 |
-XX:MaxPermSize=256m | Unrecognized VM option,JVM 无法启动 | 删除;PermGen 在 8 已移除 |
--illegal-access=permit | 警告并忽略,封装仍然生效 | 删除;改用 --add-opens |
-XX:+UseBiasedLocking | 仅警告,仍可启动(21 才拒绝) | 17 可暂时保留,但计划删除 |
-XX:+PrintGCDetails | 警告并映射到 -Xlog:gc* | 显式改成 -Xlog:gc* |
# 确认最终生效的 GC 与暂停目标
java -XX:+PrintFlagsFinal -version | grep -E "UseG1GC|MaxGCPauseMillis"
# uintx MaxGCPauseMillis = 200 {product} {default}
# bool UseG1GC = true {product} {ergonomic}
参数清理要覆盖所有入口:启动脚本、Dockerfile、Kubernetes args、IDE 运行配置、测试容器。只在本地 java -jar 改掉,线上仍会启动失败。
升级检查清单05
- 把本地、CI、容器、构建插件统一到 JDK 17,确认 class 文件版本为 61。
- 用
--release 17编译,并开启-Xlint:removal;先让所有 removal 警告可见。 - 跑
jdeps --jdk-internals -R与jdeprscan --release 17 --for-removal,列出内部 API 与待移除 API。 - 清理 JVM 参数:CMS、MaxPermSize、--illegal-access;确认 UseBiasedLocking 只是警告而非硬失败。
- 处理强封装:升级依赖,替换 sun.* / jdk.internal.*;确需兼容时才加最小范围 --add-opens。
- 确认 Security Manager 用法:17 仍可运行但有警告,计划在升级到 18+ 前移除。
- 检查 RMI Activation、Applet、AOT 等移除 / 弃用项,删除对应代码与启动脚本。
- 跑完整测试与压测,观察 GC 日志、启动时间、反射错误和依赖加载。
迁移完成的标准不是「能启动」,而是「启动参数干净、没有依赖 JDK 内部 API、测试与压测通过、回滚路径明确」。
升级到 17 不等于必须使用 17 的所有新语法。先把运行时升稳,再按团队熟悉度逐步引入 record / sealed;预览特性单独隔离,不要混进主路径。
版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| 强封装 JDK 内部 | 17 正式 | JEP 403;--illegal-access 被移除。 |
| RMI Activation | 17 移除 | JEP 407;java.rmi 仍在。 |
| Security Manager | 17 弃用 / 21 默认禁用 / 24 永久禁用 | 17 默认仍可安装,会打印警告。 |
| Applet API | 17 弃用待移除 | JEP 398;类仍在 java.desktop 中。 |
| CMS GC | 14 移除 | 17 传入参数直接启动失败。 |
| UseBiasedLocking | 17 警告 / 21 拒绝 | 17 仍可启动,21 变成 Unrecognized VM option。 |
| ZGC / Shenandoah | 15 正式 | 17 可直接用于生产;分代 ZGC 在 21。 |
| switch 类型模式 | 17 预览 | JEP 406;--enable-preview,21 正式。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。
01. 为什么 --illegal-access=permit 在 17 上无效?
JEP 403 在 17 移除了这个选项的支持,传入只会打印警告并被忽略。JDK 内部包一律强封装,只能针对具体包使用 --add-opens。
02. Security Manager 在 17 和 21 的行为有什么区别?
17 只是弃用,默认仍允许安装,会打印终止性弃用警告;21 默认禁用,调用 setSecurityManager 抛 UnsupportedOperationException;24 永久禁用。
03. 升级到 17 时,哪些 JVM 参数会让 JVM 直接拒绝启动?
CMS 相关参数(UseConcMarkSweepGC)和 PermGen 相关参数(MaxPermSize)会报 Unrecognized VM option 并退出。illegal-access 只会警告,UseBiasedLocking 在 17 也只是警告。
04. 遇到 InaccessibleObjectException 应该先加 --add-opens 吗?
不应该。先确认哪个库在反射访问 JDK 内部包,优先升级或替换该库;--add-opens 只作为临时兼容,并限定到最小包与模块。
学习进度
记录保存在本机浏览器 · 不上传