JDK 9~17 新特性及升级建议
总览
版本 | 类别 | 特性名称 | 详细说明 | 升级建议 |
---|---|---|---|---|
JDK 9 | 语言 | 接口私有方法 | 接口可定义 private 方法,减少重复默认实现 | 对旧代码无影响,可在接口中重构公共逻辑 |
语言 | 流 API 增强 | 新增 takeWhile 、dropWhile 、iterate | 无兼容性问题,可优化流操作 | |
API | 多版本 JAR | 同一 JAR 支持多版本类实现 | 适合兼容多 JDK 版本的库开发 | |
平台 | 模块化系统(Project Jigsaw) | module 系统,模块化打包与依赖隔离 | 高影响:需处理 split packages 、反射访问内部 API | |
JDK 10 | 语言 | 局部变量类型推断 (var ) | 自动推断局部变量类型 | 无兼容性问题,代码可读性需平衡 |
JVM | G1 GC 改进 | Full GC 并行化 | 性能提升,可直接受益 | |
JVM | 应用类数据共享(AppCDS) | 类元数据共享,加快启动 | 需打包时生成共享档案 | |
JVM | Thread-Local Handshakes | 单线程操作无需全局停顿 | 性能提升,无需代码改动 | |
JDK 11 | API | HTTP Client(标准化) | 支持 HTTP/1.1 & HTTP/2,异步调用 | 可替换旧 HttpURLConnection |
语言 | Lambda 参数 var | Lambda 参数可用 var | 无兼容性问题 | |
API | 字符串新方法 | isBlank 、lines 、strip 、repeat | 仅 JDK 11+ 可用,低版本需兼容处理 | |
平台 | 移除 Java EE / CORBA 模块 | 删除旧 API 模块 | 高影响:依赖需改用 Jakarta EE 等替代方案 | |
JDK 12 | 语言 | Switch 表达式(预览) | switch 返回值、-> 语法 | 预览特性,需 --enable-preview |
JVM | Shenandoah GC(实验) | 低延迟 GC | 低延迟需求可考虑启用 | |
JVM | G1 改进 | 提升混合回收性能 | 无兼容性问题 | |
JDK 13 | 语言 | 文本块(预览) | """ 多行字符串 | 预览特性,减少转义 |
JVM | ZGC 改进 | 释放未使用内存 | 大堆内存场景有益 | |
JDK 14 | 语言 | Record(预览) | 不可变数据类 | 预览特性,适合 DTO/VO |
语言 | instanceof 模式匹配(预览) | 自动强转变量 | 预览特性,简化代码 | |
JVM | NullPointerException 精确消息 | NPE 提示变量名 | 排查更方便 | |
语言 | Switch 表达式(正式) | 从预览转标准 | 无需预览参数 | |
JDK 15 | 语言 | 密封类(Sealed Class,预览) | 限制继承类范围 | 预览特性,需 permits |
语言 | 文本块(正式) | 三引号多行字符串 | 无兼容性问题 | |
JVM | ZGC/G1 类卸载支持 | 类卸载能力增强 | 内存释放更积极 | |
JVM | 隐藏类(Hidden Class) | 动态生成类支持 | 框架可用 | |
JDK 16 | 语言 | Record(正式) | 不可变数据类标准化 | 可用于 DTO/值对象 |
语言 | instanceof 模式匹配(正式) | 自动强转 | 代码更简洁 | |
API | Vector API(孵化) | SIMD 加速 | 高性能计算可用 | |
API | Unix Domain Socket | 原生支持 Unix 域套接字 | 跨进程通信可用 | |
JDK 17 | 语言 | 密封类(正式) | 限制继承范围标准化 | 适合安全约束模型 |
语言 | switch 模式匹配(预览) | switch 支持类型模式匹配 | 预览特性,需开启 | |
平台 | JDK 内部 API 强封装 | 内部 API 默认不可访问 | 高影响:反射访问需迁移至公开 API | |
平台 | 新 macOS 渲染管线 | 基于 Metal | macOS 上图形渲染性能提升 | |
API | 外部函数与内存 API(孵化) | 替代 JNI 的新 API | 高性能原生调用可用 | |
平台 | 删除 RMI Activation 系统 | 移除过时 RMI 模块 | 若使用需迁移至自定义方案 |
模块化系统
JDK 的 模块化系统(Java Platform Module System, JPMS) 是在 JDK 9 引入的,核心目标是让 JDK 自身模块化,同时为应用提供更清晰的依赖隔离与打包能力。
这不仅是语言/平台的一次重大演进,还直接改变了 JDK 结构(JDK 8 以前的 rt.jar、tools.jar 都没了)。
1. 模块化的核心概念
概念 | 说明 | 示例 |
---|---|---|
module | 模块是包的集合,并声明依赖和导出 | module java.sql { exports java.sql; requires java.base; } |
module-info.java | 模块描述文件,位于模块根目录,用来声明模块的依赖和导出 | 见下方示例 |
exports | 声明对外暴露的包(默认不导出) | exports com.example.utils; |
requires | 声明依赖的模块(默认不隐式引入) | requires java.sql; |
opens | 运行时对反射开放(不影响编译期封装) | opens com.example.model to gson; |
provides / uses | SPI 服务声明与消费 | provides MyService with MyServiceImpl; |
2. 模块化打包与依赖隔离
2.1 模块化打包
传统 JAR → 模块化 JAR(包含
module-info.class
)打包后可使用
jlink
按需生成运行时(只包含应用需要的 JDK 模块)命令示例:
javac -d out/com.example.module src/module-info.java src/com/example/**/*.java jar --create --file com.example.module.jar -C out/com.example.module .
2.2 依赖隔离机制
模块之间默认互不可见,必须通过
requires
明确声明依赖未
exports
的包无法被外部访问(编译期直接报错)优势:
防止依赖“污染” → 避免 Classpath Hell
运行时启动更快(按需加载模块)
减少打包体积(jlink 剪裁)
3. module-info.java 示例
module com.example.app {requires java.sql; // 编译期依赖 java.sql 模块requires com.example.utils; // 依赖自定义工具模块exports com.example.app.api; // 暴露 API 包opens com.example.app.model to gson; // 对 gson 库开放反射访问uses com.example.spi.MyService; // 消费服务接口provides com.example.spi.MyService with com.example.impl.MyServiceImpl; // 提供实现 }
4. 迁移时的注意点
内部 API 强封装(见你上一个问题),很多 JDK 内部包需要通过
--add-exports
/--add-opens
临时开放,否则编译或运行失败一些第三方库(尤其是老版本)会因为反射访问被阻止而报错
打包工具(如 Maven Shade、Spring Boot fat jar)在 JPMS 下要重新配置
动态模块路径(
--module-path
)与传统classpath
是不同的加载机制
5. 可选迁移策略
场景 | 建议 |
---|---|
老项目,依赖很多反射/内部 API | 先用 --illegal-access=permit 兼容运行,逐步改代码 |
新项目 | 直接用 JPMS,按需暴露包,依赖显式声明 |
想减小部署体积 | 配合 jlink 定制 JRE |
使用 Spring Boot 等框架 | 注意部分框架(如 Hibernate)需要 opens 才能工作 |
应用类数据共享
应用类数据共享(AppCDS,Application Class-Data Sharing)是 JDK 10 起正式引入(早期 JDK 5 就有基础的 CDS 功能),在 JDK 12 之后进一步增强的 JVM 特性,主要用于 加快 JVM 启动速度、减少内存占用。
1. 背景与原理
CDS(Class Data Sharing) 最初只针对 JDK 自带的系统类(
rt.jar
、java.base
等模块里的类),将它们的元数据(metadata)提前解析好,存入共享归档文件(archive file)。AppCDS 将这个机制扩展到 用户应用的类,即可以让你自己写的类也享受“提前解析+共享加载”的好处。
归档文件里存的不是
.class
字节码,而是经过 类加载、链接(linking)后 JVM 内部结构(klass metadata、常量池等),因此 JVM 启动时直接映射到内存,不必重复解析字节码。
好处:
启动速度提升
省掉类加载 & 验证 & 解析元数据的时间。
内存占用降低
多个 JVM 实例可以共享同一份类元数据映射(内存映射文件),不必各自保存一份。
2. 版本演进
JDK 版本 | 特性变化 |
---|---|
JDK 5 | 首次引入 CDS(仅系统类) |
JDK 9 | 模块化后,CDS 支持模块归档 |
JDK 10 | AppCDS 引入,支持用户类归档,但需要手动两步生成(dump → use) |
JDK 12 | AppCDS 归档文件生成命令简化,支持动态归档(无需 -Xshare:dump 单独跑一次) |
JDK 13+ | 自动支持 default CDS archive (开箱即用) |
JDK 17 | AppCDS 稳定,支持容器环境(与 G1、ZGC 等配合) |
3. 使用方法(以 JDK 17 为例)
3.1 生成归档
两种方式:
方式 1:两步生成
# 1. 运行应用生成类列表
java -Xshare:off -XX:DumpLoadedClassList=classes.lst -cp app.jar com.example.Main
# 2. 根据类列表生成共享归档
java -Xshare:dump \ -XX:SharedClassListFile=classes.lst \ -XX:SharedArchiveFile=appcds.jsa \ -cp app.jar
方式 2:一键生成(JDK 12+ 动态 CDS)
java -XX:ArchiveClassesAtExit=appcds.jsa -cp app.jar com.example.Main
运行结束后会生成 appcds.jsa
文件。
3.2 使用归档
java -Xshare:on -XX:SharedArchiveFile=appcds.jsa -cp app.jar com.example.Main
4. 限制与注意事项
类路径 / 模块路径 要一致
AppCDS 归档和运行时 classpath/modulepath 必须匹配,否则 JVM 会忽略不兼容的类。
归档不可移植
归档文件和生成它的 JVM 版本、OS、架构绑定(不能跨平台或跨不同 JDK 版本用)。
仅共享类元数据,不共享字节码
方法字节码依然需要 JIT 编译,AppCDS 主要优化启动和内存占用。
动态类加载除外
如反射生成的类、运行时生成代理类等不会进入归档。
5. 最佳实践(迁移建议)
建议场景:
微服务 / Serverless 场景(频繁启动)
多 JVM 实例并行运行(容器集群)
冷启动性能敏感的 CLI 工具
优化建议:
在 CI/CD 里加一个 AppCDS 构建阶段,生成归档文件随应用一同部署。
对 classpath 做版本锁定,避免启动时因为 jar 版本变动导致归档失效。
配合
G1GC
、ZGC
等现代 GC 使用,最大化启动 & 内存收益。