拓冰建站拓冰建站
首页 / 资讯中心 / 正文

JDK 1.8 升级 JDK 17:rt.jar 消失与 Base64 兼容性实战指南

1. 为什么这次升级不是“换个版本号”那么简单JDK 1.8 到 JDK 17 的跨越绝不是在 IDE 里点几下鼠标、改个数字就能搞定的“小更新”。它是一次横跨近十年、历经九个重大版本迭代JDK 9 到 JDK 17的结构性演进。我亲手主导过三个中大型 Spring Boot 项目从 1.8 迁移到 17 的全过程最深的体会是这不是一次升级而是一次对整个 Java 生态认知的重装。你面对的不是新 API 的学习成本而是 JVM 底层机制、类加载模型、安全策略、甚至构建工具链的全面重构。核心关键词“rt.jar”和“java.util.Base64”就是这场变革最锋利的切口。在 JDK 1.8 时代“rt.jar”是 Java 运行时的“心脏”所有核心类——java.lang.*、java.util.*、java.io.*——都打包在里面开发者习惯性地把它当作“不可动摇的基石”。而 JDK 17 早已没有rt.jar这个概念取而代之的是模块化系统Jigsaw下的java.base模块它被编译成更高效的.jimage格式直接由 JVM 加载。这意味着任何依赖rt.jar路径硬编码、或通过反射暴力访问内部 API比如sun.misc.Unsafe的代码在 JDK 17 上会直接抛出NoClassDefFoundError或InaccessibleObjectException。这不是 Bug是设计哲学的彻底转向。另一个高频热词java.util.Base64则揭示了 API 层面的“温柔一刀”。JDK 1.8 引入了java.util.Base64但很多老项目还在用 Apache Commons Codec 或sun.misc.BASE64Encoder。到了 JDK 17后者已被完全移除前者虽保留但其行为细节如换行符处理、填充规则在不同 JDK 版本间存在微妙差异。一个在 1.8 下能完美解密的 Base64 字符串在 17 下可能因默认编码器配置不同而失败。这背后是 Java 对“向后兼容”的重新定义它保证的是二进制兼容.class文件能在新 JVM 上跑而非源码兼容.java文件不加修改就能编译。很多问题恰恰就卡在这条分界线上。所以当你搜索“jdk17安装包下载”或“jdk环境变量配置失败”时真正卡住你的往往不是环境变量本身而是旧项目里那些深埋的、与rt.jar紧耦合的类加载逻辑或是某个第三方库里偷偷调用的sun.*包。这就是为什么“jdk安装教程”千篇一律而“JDK 从 1.8 升级到 JDK17 的问题汇总”却成了无数工程师深夜加班的痛点。它解决的不是“怎么装”而是“装完之后我的世界为什么崩塌了”。2. 升级路径设计为什么跳过中间版本是最大陷阱很多人看到“JDK 1.8 → JDK 17”第一反应是“一步到位”仿佛版本号越大越好。我必须坦白这是我踩过最深的坑。在第一个项目里我们直接从 1.8 跳到了 17结果 CI 流水线连续崩溃三天日志里满屏都是Unsupported major.minor version 61JDK 17 的 class 文件版本号和java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext。后来复盘才发现这种“跨越式升级”等于把一座百年老桥的桥墩全拆了再用新材料建一座新桥却不检查桥面的每一块木板是否还能适配新桥墩。2.1 为什么必须分阶段——看懂 JDK 的“断崖式”演进JDK 的版本演进并非平滑曲线而是几个关键的“断崖点”。其中JDK 9 和 JDK 11 是两个无法绕过的里程碑JDK 92017年引入了模块化系统Jigsaw这是rt.jar消失的起点。它同时移除了java.xml.bindJAXB、java.xml.wsJAX-WS等企业级 API这些 API 在 1.8 中是内置的但在 9 中变成了可选模块。如果你的项目用了XmlRootElement注解或者调用了JAXBContext.newInstance()那么 JDK 9 就是第一个“拦路虎”。JDK 112018年作为第一个长期支持LTS版本它正式将java.xml.bind等模块从 JDK 中剥离并要求开发者显式添加依赖如jakarta.xml.bind:jakarta.xml.bind-api。更重要的是它开始强化对内部 API 的访问限制--illegal-accessdeny成为默认策略任何试图反射调用sun.*包的行为都会被拦截。JDK 172021年则是在 11 的基础上进一步收紧了安全策略并完成了对java.security、java.net.http等模块的现代化改造。因此正确的升级路径应该是1.8 → 11 → 17。先让项目在 JDK 11 上稳定运行解决掉 JAXB、JAX-WS 等“大块头”兼容问题再升级到 17去处理java.net.http的异步 API 替换、ConcurrentHashMap的内部结构变更等细节问题。跳过 11等于把两个时代的兼容性问题压缩到一次解决复杂度呈指数级增长。2.2 工具链的“隐形墙”Maven、Gradle、IDE 不是摆设升级 JDK绝不能只盯着JAVA_HOME。你的构建工具和开发环境本身就是一套独立的生态系统它们有自己的 JDK 兼容性列表。MavenMaven 3.5.x 及以下版本对 JDK 17 支持极差。我曾遇到一个项目pom.xml里maven.compiler.source和maven.compiler.target都设为17但 Maven 自身运行在 JDK 1.8 下导致编译器插件根本无法识别17这个值报错Unsupported target value 17。解决方案是Maven 必须升级到 3.8.1且其启动脚本mvn必须指向 JDK 17 的java命令。这需要修改MAVEN_HOME/bin/mvn脚本中的JAVA_HOME而不是仅仅设置系统的JAVA_HOME。GradleGradle 6.x 是分水岭。Gradle 6.0 开始原生支持 JDK 14但对 JDK 17 的完整支持要等到 Gradle 7.0。如果你用的是 Gradle 5.x即使项目代码没问题gradle build也会在解析module-info.java时失败。一个简单验证方法在终端执行gradle --version确认其输出的JVM版本与你期望的 JDK 版本一致。IDEIntelliJ IDEA / EclipseIDE 的“JDK 配置”有两层含义。第一层是 IDE 自身的运行环境即 IDE 启动时用的 JDK第二层是项目 SDK即编译和运行项目时用的 JDK。很多人只改了第二层忘了第一层。例如IDEA 2020.1 默认最高支持 JDK 15如果你强行用它打开 JDK 17 项目编辑器会频繁卡死代码提示失效。正确做法是先升级 IDE 到 2021.1 版本再在File Project Structure Project中设置 Project SDK 为 JDK 17最后在File Settings Build Compiler Java Compiler中确认 Target bytecode version 也为 17。提示在升级前务必在pom.xml或build.gradle中明确声明sourceCompatibility和targetCompatibility。不要依赖 IDE 的自动推断因为 IDE 的推断逻辑有时会滞后于 JDK 的实际能力。3. 核心问题深度解析从rt.jar到ConcurrentHashMap的实战避坑指南升级过程中暴露的问题90% 都集中在几个经典领域类路径与模块化、废弃 API 替换、并发集合行为变更、以及构建与测试的隐式依赖。下面我将结合真实故障案例逐个拆解。3.1rt.jar的幽灵当“找不到类”成为常态rt.jar的消失最直接的后果就是大量“找不到类”的错误。但这背后往往藏着两种截然不同的原因。第一种硬编码路径的遗留代码有些老项目为了“优化”类加载会手动将rt.jar的路径加入ClassLoader。例如URL rtJarUrl new URL(file:///usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar); URLClassLoader classLoader new URLClassLoader(new URL[]{rtJarUrl});这段代码在 JDK 17 下必然失败因为rt.jar已不复存在。解决方案不是找一个替代 jar而是彻底重构。JVM 的核心类现在由java.base模块提供你不需要、也不应该手动加载它们。所有对java.*包的引用都应该通过标准的import语句完成由 JVM 的启动类加载器Bootstrap ClassLoader自动处理。第二种反射调用内部 API 的“黑魔法”这是最隐蔽也最危险的问题。很多性能敏感的代码会用sun.misc.Unsafe来实现无锁队列或对象字段偏移量计算。例如Field unsafeField Unsafe.class.getDeclaredField(theUnsafe); unsafeField.setAccessible(true); Unsafe unsafe (Unsafe) unsafeField.get(null);在 JDK 9 中sun.misc.Unsafe被保留在jdk.unsupported模块中但默认禁止访问。直接运行会抛出InaccessibleObjectException。官方推荐的替代方案是VarHandle它是 JDK 9 引入的标准化、安全的内存操作接口。将上面的代码改为// 获取一个 VarHandle用于操作某个字段 VarHandle handle MethodHandles.privateLookupIn(MyClass.class, MethodHandles.lookup()) .findVarHandle(MyClass.class, myField, int.class); // 使用 VarHandle 进行原子操作 handle.compareAndSet(myObject, expected, updated);VarHandle的语法比Unsafe稍长但它经过了严格的 JVM 安全校验且在所有 LTS 版本中都保持稳定。注意不要尝试用--add-opens参数来“绕过”限制例如--add-opens java.base/java.langALL-UNNAMED。这只是一个临时补丁会在未来的 JDK 版本中被彻底移除。真正的解决方案是拥抱VarHandle、java.util.concurrent.atomic等现代 API。3.2java.util.Base64的“坑”一个换行符引发的血案Base64看似简单却是升级中最容易被忽视的“雷区”。问题根源在于JDK 1.8 和 JDK 17 的Base64编码器默认行为不同。JDK 1.8 的Base64.getEncoder()默认是“MIME 编码器”它会在每 76 个字符后插入一个换行符\n。JDK 17 的Base64.getEncoder()默认是“基本编码器”它不插入任何换行符。这意味着一段在 JDK 1.8 下生成的、带换行符的 Base64 字符串如果直接用 JDK 17 的默认解码器去解会因为换行符而失败。我遇到过一个真实案例一个支付网关的签名验签逻辑服务端用 JDK 1.8 生成签名客户端用 JDK 17 解析结果每次验签都失败。排查了两天最终发现是 Base64 字符串里混入了\n。解决方案非常简单但必须显式声明// 如果你需要兼容 JDK 1.8 的 MIME 行为使用 Base64.Encoder mimeEncoder Base64.getMimeEncoder(); String encoded mimeEncoder.encodeToString(data); // 如果你只需要纯 Base64无换行使用 Base64.Encoder basicEncoder Base64.getEncoder(); String encoded basicEncoder.encodeToString(data); // 解码时同样要匹配编码器类型 byte[] decoded Base64.getMimeDecoder().decode(encoded); // 对应 MIME 编码 // 或 byte[] decoded Base64.getDecoder().decode(encoded); // 对应基本编码实操心得在项目升级初期建议全局搜索所有Base64.getEncoder()和Base64.getDecoder()的调用点统一替换为getMimeEncoder()/getMimeDecoder()并添加单元测试确保编码/解码结果在 JDK 1.8 和 17 下完全一致。这是一个“一劳永逸”的小动作能避免后期无数个深夜的线上排查。3.3ConcurrentHashMap的“静默变更”从 1.7 到 1.8 再到 17关于ConcurrentHashMap的区别网络上流传着“1.7 是分段锁1.8 是 CAS synchronized”的说法。这没错但到了 JDK 17它的内部结构又经历了一次重要演进引入了TreeBin结构来优化高冲突场景下的性能。这个变更本身是向后的但会暴露一个隐藏的、致命的假设很多老代码会直接遍历ConcurrentHashMap的entrySet()并假设返回的Map.Entry是java.util.HashMap.Node的实例。例如for (Map.EntryK, V entry : map.entrySet()) { // 错误假设 entry 是 HashMap.Node if (entry instanceof HashMap.Node) { // ... 执行一些底层操作 } }在 JDK 17 中ConcurrentHashMap的entrySet()返回的Entry是一个内部的MapEntry类它不是HashMap.Node的子类。这段代码会永远走不到if分支导致逻辑失效。正确的做法是永远只依赖Map.Entry接口定义的契约for (Map.EntryK, V entry : map.entrySet()) { K key entry.getKey(); // 安全 V value entry.getValue(); // 安全 // 不要尝试向下转型 }此外JDK 17 还增强了ConcurrentHashMap的computeIfAbsent方法的原子性保证。在 1.8 中如果mappingFunction抛出异常该键值对会被清除而在 17 中它会保留原始值。如果你的业务逻辑依赖于旧的异常清除行为就需要在mappingFunction内部手动处理异常而不是依赖框架。4. 实操全流程从环境准备到生产验证的七步法升级不是一蹴而就的魔法而是一个严谨的工程流程。我总结了一套经过三个项目验证的“七步法”每一步都有明确的目标和交付物确保升级过程可控、可回滚。4.1 第一步环境隔离与 JDK 17 的“纯净”安装一切始于一个干净的环境。不要在现有开发机上直接覆盖安装。我强烈建议使用容器或虚拟机来创建一个隔离的 JDK 17 环境。Windows 用户下载 Adoptium现为 Eclipse Temurin的 JDK 17 Windows x64 MSI 安装包。安装时取消勾选“Set JAVA_HOME”。因为我们需要手动、精确地控制JAVA_HOME避免被安装程序的默认值干扰。Linux/macOS 用户推荐使用 SDKMAN! 工具它能让你在同一台机器上轻松管理多个 JDK 版本# 安装 SDKMAN! curl -s https://get.sdkman.io | bash source $HOME/.sdkman/bin/sdkman-init.sh # 安装 JDK 17 sdk install java 17.0.1-tem # 设置为当前会话默认 sdk default java 17.0.1-tem安装完成后验证java -version # 输出应为openjdk version 17.0.1 ... java -XshowSettings:properties -version 21 | grep java.home # 输出应为java.home /path/to/jdk-17.0.1关键点java.home的路径必须是你刚刚安装的 JDK 17 的根目录而不是某个子目录如jre。很多“jdk环境变量配置失败”的问题根源就在于JAVA_HOME指向了错误的路径。4.2 第二步构建工具链的“双轨制”切换不要急于修改主分支。在 Git 中创建一个feature/jdk17-migration分支并在这个分支上进行所有升级工作。Maven 项目在pom.xml的properties中添加maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target maven.compiler.release17/maven.compiler.release !-- 关键启用 release 模式 --release参数是 JDK 9 引入的它强制编译器只使用指定版本的 API避免意外调用到更高版本才有的方法。这是防止“编译通过运行失败”的最有效手段。Gradle 项目在build.gradle中java { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 }然后在项目根目录下创建一个jdk17-build.sh脚本内容为#!/bin/bash # 显式指定 Maven 使用 JDK 17 export JAVA_HOME/path/to/jdk-17.0.1 ./mvnw clean compile这样无论你本地的JAVA_HOME是什么这个脚本都能确保构建过程 100% 使用 JDK 17。4.3 第三步静态扫描——用工具提前“排雷”在编译之前先用工具扫描代码找出所有潜在的兼容性问题。我推荐两个免费、强大的工具JDK Migration Assistant (JMA)这是 Oracle 官方提供的工具能分析.jar文件报告所有不兼容的 API 调用。下载地址在 Oracle 官网搜索即可。用法jma -jar my-app.jar -report-dir ./jma-report它会生成一个 HTML 报告清晰列出所有sun.*、com.sun.*等内部 API 的调用位置。Revapi一个开源的 API 演化分析工具可以集成到 Maven 生命周期中。在pom.xml中添加plugin groupIdorg.revapi/groupId artifactIdrevapi-maven-plugin/artifactId version0.14.3/version configuration analysisConfiguration revapi.java.version17/revapi.java.version /analysisConfiguration /configuration /plugin运行mvn revapi:check它会对比你的代码与 JDK 17 的 API 规范报告所有不兼容的变更。这两步做完你手上就有一份精准的“问题地图”知道哪些文件、哪几行代码需要重点攻坚。4.4 第四步依赖库的“生死簿”审查第三方库是升级路上最大的不确定因素。一个spring-boot-starter-web依赖背后可能牵扯几十个 transitive 依赖。我们必须建立一份“依赖生死簿”。第一步生成依赖树Maven:mvn dependency:tree -Dincludesorg.springframework.boot:spring-boot-starter-webGradle:./gradlew dependencies --configuration compileClasspath第二步逐个审查重点关注以下几类库已归档EOL的库如commons-httpclient2010年停止维护、log4j 1.x2015年 EOL。这些库在 JDK 17 下几乎必然失败必须替换为httpclientApache HttpComponents或log4j2。强绑定 JDK 版本的库如jaxb-api。JDK 1.8 内置JDK 11 需要显式添加jakarta.xml.bind:jakarta.xml.bind-api。使用sun.*API 的库如某些老版本的netty或guava。查它们的 GitHub Issues看是否有针对 JDK 17 的修复 PR。第三步制定替换策略旧依赖JDK 1.8 状态JDK 17 替代方案备注javax.xml.bind:jaxb-api内置jakarta.xml.bind:jakarta.xml.bind-api注意javax→jakarta的命名空间变更com.sun.xml.bind:jaxb-impl内置org.glassfish.jaxb:jaxb-runtimeGlassFish 是 Jakarta EE 的参考实现commons-codec:commons-codec第三方继续使用但需升级到1.151.15 版本已完全移除对sun.misc.BASE64Encoder的依赖4.5 第五步单元测试的“压力测试”编译通过只是万里长征第一步。真正的考验是测试。运行所有单元测试mvn test或./gradlew test。这是最基础的门槛。如果测试失败优先查看java.lang.UnsupportedClassVersionError说明某个依赖还是用旧 JDK 编译的和java.lang.NoClassDefFoundError说明某个模块缺失。启用 JVM 诊断参数在测试命令中加入-XX:UnlockDiagnosticVMOptions -XX:PrintSharedArchiveAndExit它可以帮你快速定位类加载问题。特别关注“时间敏感”测试JDK 17 对java.timeAPI 进行了微调某些Instant.now()的精度在不同版本下可能有纳秒级差异。如果测试里写了assertEquals(expectedTime, actualTime)可能会因为精度问题失败。应改为assertTrue(Math.abs(expectedTime.toEpochMilli() - actualTime.toEpochMilli()) 10)。4.6 第六步集成测试与性能基线对比单元测试通过后进入集成测试阶段。这时要启动完整的应用上下文Spring Context连接真实的数据库、消息队列。关键指标监控在 JDK 1.8 和 JDK 17 下分别运行相同的压测脚本如 JMeter记录以下指标启动时间Application started in X secondsGC 时间占比通过-Xlog:gc*日志分析平均响应时间P95, P99内存占用jstat -gc pid你会发现JDK 17 的启动时间通常比 1.8 快 15%-20%GC 压力显著降低得益于 ZGC 或 Shenandoah 的成熟但某些复杂的反射操作如 Spring 的Autowired在首次初始化时可能稍慢。这些都是正常现象只要整体性能不劣化就可以接受。4.7 第七步灰度发布与生产回滚预案上线不是终点而是新阶段的开始。灰度策略在 Kubernetes 环境中可以利用Service的权重路由将 1% 的流量导向 JDK 17 的 Pod。观察其error rate、latency和JVM metrics特别是java.lang:typeMemoryPool,nameMetaspace的使用率JDK 17 的 Metaspace 默认更大但泄漏风险也更高。回滚预案必须在发布前准备好一键回滚脚本。对于 Docker 镜像这意味着你要保留 JDK 1.8 镜像的 tag如myapp:v1.0-jdk8并在 CI/CD 流水线中配置一个rollback-to-jdk8的 Job。回滚的时间窗口必须控制在 5 分钟以内这是 SRE 团队能接受的最长故障时间。5. 常见问题速查表与独家避坑技巧以下是我在三次升级实战中整理出的最常遇到的 10 个问题及其“一招制敌”的解决方案。这些问题90% 的工程师都会在凌晨两点的 Slack 群里问而答案就在这里。问题现象根本原因一行命令/代码解决避坑技巧Error: Could not find or load main classJAVA_HOME指向了 JDK 的jre目录而非 JDK 根目录export JAVA_HOME$(dirname $(dirname $(readlink -f $(which java))))在 Linux/macOS 上永远用readlink -f获取java的真实路径再向上两级java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContextJAXB API 在 JDK 11 中被移除dependencygroupIdjakarta.xml.bind/groupIdartifactIdjakarta.xml.bind-api/artifactIdversion3.0.1/version/dependency不要加javax.xml.bind:jaxb-api那是旧的 Jakarta 命名空间target is not a jdk root. system library was not found.(IDEA)IDEA 的 Project SDK 设置指向了一个 JRE而非 JDKFile Project Structure Project Project SDK点击New... JDK选择 JDK 17 的根目录JDK 17 的安装包里jre目录已不存在整个目录就是 JDKCaused by: java.lang.ClassNotFoundException: sun.misc.BASE64Encoder代码直接引用了已移除的sun.*类替换为java.util.Base64.getEncoder()全局搜索sun\.misc\.一个都不能留java.util.ServiceConfigurationError: javax.xml.parsers.DocumentBuilderFactory: Provider org.apache.xerces.jaxp.DocumentBuilderFactoryImpl not foundXerces 库与 JDK 17 的java.xml模块冲突在pom.xml中排除xerces:xercesImplJDK 17 自带 XML 解析器外部 Xerces 是冗余的Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compileMaven 插件版本太低不支持release参数plugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.10.1/version/pluginMaven 编译插件必须 3.8.1java.lang.IllegalStateException: Failed to load ApplicationContext(Spring Boot)Spring Boot 版本过低不兼容 JDK 17升级spring-boot-starter-parent到2.6.0或3.0.0Spring Boot 2.5.x 是最后一个支持 JDK 17 的 2.x 版本java.lang.SecurityException: Prohibited package name: java.lang自定义类加载器试图加载java.*包下的类删除自定义类加载器中对java.*的loadClass覆盖逻辑JVM 的 Bootstrap ClassLoader 专管java.*你不能抢它的活java.lang.OutOfMemoryError: MetaspaceJDK 17 的 Metaspace 默认大小比 1.8 小启动参数添加-XX:MaxMetaspaceSize512m不要盲目加大先用jstat -gc pid查看 Metaspace 使用率java.net.http.HttpClient报java.lang.NoClassDefFoundError代码在 JDK 1.8 下编译但HttpClient是 JDK 11 的 API在pom.xml中添加dependencygroupIdorg.openjdk.jdk/groupIdartifactIdjdk.httpclient/artifactIdversion17/version/dependency更好的方案是用OkHttp或Apache HttpClient作为统一 HTTP 客户端独家避坑技巧永远不要相信“网上下载的 JDK 安装包”。我见过太多人从非官方渠道下载的 JDK里面被植入了恶意的java启动脚本会在后台悄悄上传你的项目代码。请务必从 Eclipse Temurin (https://adoptium.net/)、Amazon Corretto (https://aws.amazon.com/corretto/) 或 Microsoft Build of OpenJDK (https://learn.microsoft.com/en-us/java/openjdk/download) 这些可信镜像站下载。清华镜像站https://mirrors.tuna.tsinghua.edu.cn/adoptium/是官方认可的国内镜像速度飞快可以放心使用。6. 最后的经验升级不是终点而是新旅程的起点当我第一次成功将一个拥有 200 万行代码的单体应用从 JDK 1.8 平稳迁移到 JDK 17 时团队庆祝的香槟还没开我就在监控面板上发现了一个微小的、持续上升的java.lang.ref.Finalizer对象数量。它没有触发告警但像一根细小的针扎在我心里。后来花了三天时间追踪到是某个老日志框架的Logger实例因为持有ThreadLocal的强引用导致Finalizer队列积压。这个问题在 JDK 1.8 下被 GC 的“宽松”策略掩盖了而在 JDK 17 的 ZGC 下它变得无比清晰。这让我明白JDK 升级的价值从来不只是“新功能”而是“新视角”。它像一把更精密的手术刀帮你切开那些在旧版本下被容忍、被忽略的技术债务。所以不要把这次升级看作一个待办事项To-do List上的勾选框。把它看作一次对代码健康度的全面体检。在解决rt.jar问题时顺便重构了那块混乱的类加载逻辑在替换Base64时顺手给所有加密模块加了统一的单元测试在审查依赖时果断砍掉了那个三年没更新的commons-lang2换成了commons-lang3。JDK 17 是一个成熟的 LTS 版本它带来的不仅是性能提升更是对现代 Java 开发范式的背书。当你能熟练使用Record、Sealed Classes、Pattern Matching for switch这些新特性时你的代码会变得更简洁、更安全、更易读。而这一切的起点就是今天你决定直面rt.jar的消失而不是逃避它。我个人在实际操作中的体会是升级的难度80% 在心理上20% 在技术上。只要你愿意放下“它以前能跑现在为什么不行”的执念用 JDK 17 的思维去重读每一行代码你就会发现那扇曾经紧闭的门其实一直虚掩着。推开它外面不是一片荒芜而是一个更清晰、更健壮、也更有趣的 Java 世界。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门