升级 JDK17 踩了一堆坑?这些解决办法帮我省了 3 天班

上周把公司的老项目从 JDK8 升到 JDK17,本以为改改配置就行,结果连续加班三天才搞定。那些藏在版本升级里的小陷阱,没踩过的人真的想不到。今天把遇到的问题和解决办法整理出来,说不定能帮你少加几天班。

一、编译时突然爆红?内部 API 被锁了

刚把项目 JDK 版本改成 17,编译界面红得像过年贴的春联。仔细一看,全是sun.misc.BASE64Encoder这种类找不到的错误。问了公司的老程序员才知道,JDK17 把这些内部 API 彻底封起来了,不像以前那样能随便用。

记得有个生成用户 token 的工具类,一直用sun.misc.Unsafe来做高效字符串拼接,现在直接报错。试了三种解决办法:

最省事的是加启动参数--add-exports java.base/sun.misc=ALL-UNNAMED,虽然能临时解决,但控制台警告刷个不停,像个随时会炸的炸弹,不敢用在生产环境。

稳妥点还是换标准 API:sun.misc.BASE64换成java.util.Base64,Unsafe的字符串操作改用StringBuilder。改完后测试了下性能,其实差不了多少,代码还更安全。

后来发现 JDK 自带的jdeps命令特别好用,输入jdeps --jdk-internals 你的主类名,能把所有依赖内部 API 的地方都列出来。建议升级前先跑一遍,心里有个数。

二、老依赖拖后腿,Excel 库成了绊脚石

项目里用了个 2018 年的 Excel 处理库,升级后一运行就报NoClassDefFoundError。查了文档才明白,这个库偷偷用了sun.misc包里的类,现在作者早就不维护了,只能自己想办法。

最后换了 Alibaba 的 EasyExcel,功能比老库还全,就是得改几处调用代码。改的时候发现,不光是 Excel 库,连 Spring Boot 都得升级到 2.6 以上版本才行,好在升级后很多莫名其妙的错误自动消失了。

有个同事遇到更棘手的情况,他用的某个报表工具找不到替代库,只能加--add-opens java.base/java.lang=ALL-UNNAMED参数开后门。不过他说这是权宜之计,打算年后自己重写这块功能。

三、ZGC 没想象中完美,内存多占 30%

听别人说 ZGC 垃圾回收器多厉害,启动时就加了-XX:+UseZGC参数,结果运维拿着监控图来找我:"你这升级怎么越升越费内存?"

原来 ZGC 为了保证低延迟,会预留更多内存当缓冲。后来调了两个参数好多了:

-XX:ZAllocationSpikeTolerance=5.0  # 允许短期内存波动

-XX:ZCollectionInterval=60  # 回收间隔设成60秒

改完内存占用只比原来高 5%,但系统响应速度明显变快,用户投诉都少了。不过要提醒一句,堆内存小于 4G 的项目就别折腾了,用默认的 G1 收集器足够。

四、反射代码全失效,SecurityManager 没了

登录模块里有段反射获取用户 ID 的代码,在 JDK8 里好好的,到 17 直接抛IllegalAccessException。查了才知道,JDK17 把 SecurityManager 删了,对权限控制更严了。

以前直接field.setAccessible(true)就行,现在得先问一句有没有权限:

Field field = obj.getClass().getDeclaredField("id");

if (field.canAccess(obj)) {

    // 有权限直接用

} else {

    try {

        field.setAccessible(true);

    } catch (SecurityException e) {

        // 没权限就处理异常

    }

}

项目里 11 处反射代码都得这么改,改到最后发现,其实很多地方用构造函数注入更简单,还不用担风险。

五、Lambda 当缓存 key,突然报序列化错误

用 Lambda 表达式当缓存 key 的地方,升级后报NotSerializableException。原来 JDK17 对 Lambda 的序列化加了限制,匿名实现类不能随便序列化了。

解决办法有两个:要么手动加序列化接口(Serializable & Runnable) () -> { ... },要么改用方法引用。我选了后者,代码看着还更清爽。

后来跟其他公司的程序员交流,发现大家都遇到过类似问题,看来这是个共性坑点。

六、启动脚本别照搬,有些参数早删了

原来的启动脚本里有-XX:+UseConcMarkSweepGC,在 JDK17 里直接启动失败。才想起 CMS 收集器在 JDK14 就被废弃了,17 里彻底删了。

还有-XX:+PrintGCDetails也不能用了,得换成Xlog:gc*。建议启动前先用java -XX:+PrintFlagsFinal查下参数还能不能用,别想当然地复制老脚本。

七、日期显示不对,默认时区变了

用户投诉订单时间不对,查日志发现SimpleDateFormat在 JDK17 里默认时区是 UTC,而 JDK8 默认是系统时区。这一下所有没指定时区的日期处理都出了问题。

趁机把所有日期代码都换成新 API 了:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")

    .withZone(ZoneId.of("Asia/Shanghai"));

虽然花了一下午,但彻底解决了线程安全问题,也算值了。

八、IDE 配置搞反了,编译运行版本不一致

最冤的是这个坑:pom.xml 里明明指定了 JDK17,调试时却总报错。最后发现 IDE 的 Module SDK 还是 JDK8,而运行配置用的是 17,难怪会乱套。

正确的做法是:先在 IDE 全局设置里加好 JDK17,再把项目的 SDK 和语言级别都设成 17,最后在 Maven 插件里加上<release>17</release>。

最后想说的话

升级 JDK17 前,建议先做三件事:用mvn dependency:tree查依赖兼容性,用jdeps扫内部 API,先在非核心模块试点。虽然过程有点折腾,但升级后系统的稳定性和性能提升真的明显。

现在项目跑了一个月,那些加班改 bug 的日子,都成了茶余饭后的谈资。你们升级时遇到过什么奇葩问题?来评论区分享下解决方案吧~

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容