Java8升级到Java11的实践
# 一、为什么升级?Java 8 不香了吗
# 1、升级的核心驱动力
# 1.1、性能提升
- G1 垃圾收集器优化:Java 11 中 G1 成为默认 GC,相比 Java 8 的 Parallel GC,延迟降低 30-50%
- 字符串优化:
String
内部存储从char[]
改为byte[]
,Latin-1 字符串内存占用减少 50% - 启动性能:Application Class-Data Sharing (AppCDS) 可缩短启动时间 10-20%
# 1.2、新特性支持
- 局部变量类型推断:
var
关键字让代码更简洁 - HTTP Client API:原生支持 HTTP/2,替代老旧的
HttpURLConnection
- 集合工厂方法:
List.of()
,Set.of()
,Map.of()
创建不可变集合 - 字符串增强:
isBlank()
,lines()
,strip()
,repeat()
等实用方法
# 1.3、生态系统要求
- Spring Boot 3.x 强制要求 Java 17+
- 许多新版本库开始放弃 Java 8 支持
- Oracle 已于 2022 年 3 月停止 Java 8 的公开更新
# 2、为何选择 Java 11 而非直接升级到 Java 17?
# 2.1、渐进式升级策略
Java 11 作为 LTS 版本,是从 Java 8 升级的理想跳板:
- 风险可控:Java 9-11 的变化相对温和,Java 15 移除了
Nashorn JavaScript
引擎等重要组件 - 兼容性更好:主流框架(如 Dubbo 2.x)对 Java 11 支持成熟,但对 Java 17 支持仍在完善
- 迁移成本低:大部分 Java 8 代码可以在 Java 11 上直接运行,只需少量调整
# 二、升级实战指南
# 1、升级前的准备工作
# 1.1、依赖兼容性检查
使用 jdeps
工具扫描项目依赖:
jdeps --jdk-internals --multi-release 11 your-application.jar
# 1.2、已删除功能的替代方案
删除的功能 | 影响范围 | 替代方案 |
---|---|---|
JavaFX | GUI 应用 | 添加 OpenJFX 依赖:org.openjfx:javafx-base:11+ |
字体文件 | POI、PDF 生成 | Docker 镜像添加:RUN apt-get install -y fontconfig fonts-dejavu |
Java Mission Control | 性能监控 | 单独下载 JDK Mission Control (opens new window) |
CORBA 模块 | 遗留系统集成 | 使用 GlassFish CORBA ORB 或迁移到 REST/gRPC |
Java EE 模块 | Web 应用 | 添加相应依赖,如 javax.xml.bind:jaxb-api:2.3.1 |
# 2、核心变更深度解析
# 3、模块系统(JPMS)完全指南
# 3.1、理解模块化带来的影响
Java 9 引入的模块系统是最大的架构变更,主要影响:
- 反射访问限制:默认情况下无法反射访问其他模块的内部 API
- 类路径变化:引入模块路径(module-path)概念
- 强封装:
sun.*
和com.sun.*
包被强制封装
关于模块系统,参考:Java 模块系统 (opens new window)
这里补充一下--illegal-access
参数,因为模块化的关系,如果模块中的软件包未导出或打开,但你仍然要使用这些软件包,可能会面临破坏应用程序的风险。如果在运行期反射调用内部API,可以通过添加--illegal-access=${value}
来检查,它有四个值:
参数值 | 行为 | 使用场景 | Java 17 兼容性 |
---|---|---|---|
permit | 首次违规时警告 | Java 11 默认值,快速迁移 | ❌ 已移除 |
warn | 每次违规都警告 | 开发环境,发现所有问题 | ❌ 已移除 |
debug | 警告 + 堆栈信息 | 调试定位具体代码位置 | ❌ 已移除 |
deny | 禁止所有违规访问 | 生产环境,为 Java 17 做准备 | ✅ 唯一选项 |
# 3.2、实战配置示例
渐进式迁移策略(推荐):
# 第一阶段:发现问题
java --illegal-access=warn -jar your-app.jar
# 第二阶段:逐个开放必要模块
java --illegal-access=deny \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/sun.nio.ch=ALL-UNNAMED \
--add-exports java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED \
-jar your-app.jar
# 第三阶段:针对特定框架的配置
# Spring Boot 应用
java --illegal-access=deny \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.lang.invoke=ALL-UNNAMED \
-jar spring-boot-app.jar
# 3.3、常见框架的模块配置
# Hibernate/JPA
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.nio=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
# Netty
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
--add-opens java.base/sun.nio.ch=ALL-UNNAMED
# Jackson
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.time=ALL-UNNAMED
# 4、统一 JVM 日志系统
# 4.1、日志格式迁移对照表
Java 8 参数 | Java 11 等效参数 | 说明 |
---|---|---|
-XX:+PrintGC | -Xlog:gc | 基础 GC 日志 |
-XX:+PrintGCDetails | -Xlog:gc* | 详细 GC 日志 |
-XX:+PrintGCDateStamps | -Xlog:gc*::time | 添加时间戳 |
-XX:+PrintGCApplicationStoppedTime | -Xlog:safepoint | STW 时间 |
-Xloggc:gc.log | -Xlog:gc*:file=gc.log | 输出到文件 |
# 4.2、生产环境推荐配置
# 基础配置:GC + 安全点日志
-Xlog:gc*,safepoint:file=/data/logs/jvm/gc.log:time,uptime,level,tags:filecount=10,filesize=100M
# 高级配置:包含更多诊断信息
-Xlog:gc*=info,safepoint=info,gc+ref=debug,gc+ergo=trace,gc+age=trace:file=/data/logs/jvm/gc-%t.log:time,uptime,pid,tid,level,tags:filecount=10,filesize=100M
# 性能调优配置:最小化日志开销
-Xlog:gc=info:file=/data/logs/jvm/gc.log::filecount=5,filesize=50M
# 4.3、日志解析示例
[2024-01-15T10:30:45.123+0800][0.456s][info][gc,start ] GC(12) Pause Young (Normal) (G1 Evacuation Pause)
[2024-01-15T10:30:45.145+0800][0.478s][info][gc ] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 45M->12M(256M) 22.3ms
解析要点:
[时间戳][运行时长][日志级别][标签]
:统一的日志格式GC(12)
:第 12 次 GC45M->12M(256M)
:堆内存从 45M 降到 12M,总大小 256M22.3ms
:GC 耗时
# 5、三方库兼容性问题及解决方案
# 5.1、常见依赖升级指南
库名称 | Java 8 版本 | Java 11 最低版本 | 说明 |
---|---|---|---|
Spring Boot | 2.x | 2.2.0+ | 2.1.x 部分支持 |
Spring Framework | 5.0.x | 5.1.0+ | 完全支持模块系统 |
Hibernate | 5.2.x | 5.3.7+ | 支持 Java 11 字节码 |
Jackson | 2.8.x | 2.10.0+ | 修复反射访问问题 |
Lombok | 1.16.x | 1.18.4+ | 支持 Java 11 编译 |
Mockito | 2.x | 2.23.0+ | 支持新的字节码格式 |
Byte Buddy | 1.8.x | 1.9.0+ | 动态代理兼容 |
ASM | 6.x | 7.0+ | 字节码操作支持 |
# 5.2、具体问题案例
# a、Javassist 字节码生成问题
问题:CtClass.toClass()
方法签名变更
// Java 8 写法(已废弃)
ctClass.toClass(classLoader, neighborClass.getProtectionDomain());
// Java 11 写法
ctClass.toClass(neighborClass);
解决方案:升级到 Javassist 3.24.0+ 或修改调用方式
# b、SSL/TLS 提供者冲突
问题:javax.net.ssl.SSLProtocolException: Cannot decode named group: x25519
// 问题代码(某些旧版加密库)
Security.removeProvider("SunEC"); // 不要这样做!
解决方案:
- 升级加密库到支持 Java 11 的版本
- 使用
--add-exports
导出必要的安全模块 - 避免移除系统安全提供者
# c、接口默认方法反射调用
问题:不同 Java 版本的 MethodHandle
API 变化
// Java 8 方式
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);
// Java 11 方式(需要开放模块)
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(
targetClass, MethodHandles.lookup());
MethodHandle handle = lookup.findSpecial(
interfaceClass, methodName, methodType, targetClass);
解决方案:使用条件编译或升级到统一的反射库
# 6、JVM 参数迁移指南
# 6.1、重要参数变化对照表
Java 8 参数 | Java 11 替代 | 影响说明 |
---|---|---|
-XX:+UseConcMarkSweepGC | -XX:+UseG1GC | CMS 已废弃,G1 成为默认 |
-XX:+PrintGC | -Xlog:gc | 统一日志系统 |
-XX:+PrintGCDetails | -Xlog:gc* | 详细 GC 日志 |
-XX:+UnlockCommercialFeatures | 无需 | JFR 已免费 |
-XX:+FlightRecorder | 默认可用 | 无需解锁 |
-XX:MaxPermSize | 已移除 | 元空间自动管理 |
-XX:PermSize | 已移除 | 使用 -XX:MetaspaceSize |
# 6.2、快速检测工具
# 检测 JVM 参数兼容性
java -XX:+PrintCommandLineFlags -version
# 使用 JaCoLine 在线检测
# 访问 https://jacoline.dev/inspect 上传你的启动脚本
# 6.3、生产环境 JVM 参数模板
# Java 11 生产环境推荐配置
java -server \
-Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=16m \
-XX:+ParallelRefProcEnabled \
-XX:+UnlockExperimentalVMOptions \
-XX:+UnlockDiagnosticVMOptions \
-XX:+AlwaysPreTouch \
-XX:+UseStringDeduplication \
-XX:+DisableExplicitGC \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags:filecount=10,filesize=100M \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heapdump.hprof \
-XX:ErrorFile=/logs/hs_err_pid%p.log \
-jar your-application.jar
# 三、常见陷阱与最佳实践
# 1、升级过程中的常见问题
# 1.1、编译期问题
问题 | 原因 | 解决方案 |
---|---|---|
package javax.xml.bind does not exist | JAXB 已从 JDK 移除 | 添加依赖:javax.xml.bind:jaxb-api:2.3.1 |
cannot find symbol: class Generated | javax.annotation 包移除 | 添加:javax.annotation:javax.annotation-api:1.3.2 |
illegal reflective access 警告 | 模块系统限制 | 使用 --add-opens 参数 |
Maven 编译失败 | 插件版本过旧 | 升级 maven-compiler-plugin 到 3.8.0+ |
# 1.2、运行期问题
// 问题:ClassNotFoundException: sun.misc.BASE64Encoder
// Java 8 代码
import sun.misc.BASE64Encoder;
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(data);
// Java 11 解决方案
import java.util.Base64;
String encoded = Base64.getEncoder().encodeToString(data);
# 1.3、Docker 镜像问题
# 问题:Alpine Linux 缺少字体导致 POI 等库报错
FROM openjdk:11-jre-alpine
# 解决方案:安装必要的系统库
RUN apk add --no-cache \
fontconfig \
ttf-dejavu \
&& rm -rf /var/cache/apk/*
# 或使用完整的基础镜像
FROM openjdk:11-jre-slim
# 2、升级检查清单
[ ] 依赖检查
- 运行
mvn dependency:tree
查看所有依赖版本 - 使用
jdeps
扫描内部 API 使用情况 - 更新所有不兼容的依赖到支持 Java 11 的版本
- 运行
[ ] 代码审查
- 搜索并替换
sun.*
和com.sun.*
包的使用 - 检查反射代码,添加必要的模块开放参数
- 替换已废弃的 API 调用
- 搜索并替换
[ ] 构建配置
- 更新 Maven/Gradle 插件版本
- 设置正确的源码和目标版本
- 配置模块路径(如果使用模块系统)
[ ] 运行时配置
- 迁移 JVM 参数到新格式
- 配置日志输出路径和格式
- 添加必要的
--add-opens
和--add-exports
[ ] 测试验证
- 运行完整的单元测试套件
- 执行集成测试和性能测试
- 在预生产环境进行充分验证
# 3、性能优化建议
G1GC 调优
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1ReservePercent=15 -XX:InitiatingHeapOccupancyPercent=45
字符串去重
-XX:+UseStringDeduplication -XX:StringDeduplicationAgeThreshold=3
AppCDS 应用
# 生成类列表 java -XX:DumpLoadedClassList=classes.lst -jar app.jar # 创建共享归档 java -Xshare:dump -XX:SharedClassListFile=classes.lst -XX:SharedArchiveFile=app.jsa # 使用共享归档启动 java -Xshare:on -XX:SharedArchiveFile=app.jsa -jar app.jar
# 四、总结
从 Java 8 升级到 Java 11 是一个值得投入的技术改造:
✅ 性能提升显著:G1 GC 优化、字符串内存优化等带来 20-30% 的性能提升
✅ 代码更加简洁:var
关键字、集合工厂方法、字符串新 API 提升开发效率
✅ 为未来做准备:作为 LTS 版本的跳板,便于后续升级到 Java 17/21
升级过程中最关键的是:
- 做好充分的兼容性测试
- 渐进式迁移,先在开发环境验证
- 合理使用模块系统的开放参数
- 及时更新三方依赖版本
记住,升级不是目的,持续演进才能保持技术栈的活力!
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/08/15