迁移到 JDK 8
学完你要能自己回答
- JDK 8 的 class 版本、默认 GC 和内存模型有哪些变化?
- 为什么
-XX:MaxPermSize在 8 上只是警告,在 9+ 却会让 JVM 无法启动? - 升级到 8 时,哪些工具链和兼容性问题最容易被忽略?
先确认版本坐标01
JDK 8 的 class 文件版本是 52,默认 GC 是 Parallel(服务端模式),PermGen 已被 Metaspace 取代,Java EE 模块仍在 JDK 里。升级前先把源码、编译工具、运行时、依赖四者的版本对齐。
| 项目 | JDK 7 | JDK 8 |
|---|---|---|
| class 版本 | 51 | 52 |
| 默认 GC(server) | Parallel | Parallel |
| 永久代 | PermGen | Metaspace(本地内存) |
| 日期 API | Date / Calendar | java.time |
| 接口方法 | 抽象方法 | 可加 default / static |
| 模块系统 | 无 | 无(9 才引入) |
java -version
javac -version
javap -verbose App.class | findstr major
# Linux / macOS: javap -verbose App.class | grep major
major version: 52
UnsupportedClassVersionError 会直接给出 class 文件版本,例如 class file version 52.0。它说明运行应用的 JDK 比编译时使用的 JDK 旧,或者依赖里有更高版本编译的 jar。
-source / -target 与真正的 API 边界02
JDK 8 的 javac 没有 --release(9 才加入)。-source 1.7 只限制语法和字节码版本,不会检查你用了哪些 API;真正的 7 API 边界要靠 -bootclasspath 指向 7 的 rt.jar。
import java.util.Arrays;
import java.util.List;
public class Source7Demo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ada", "Alan");
names.forEach(n -> System.out.println(n));
}
}
javac -source 1.7 -target 1.7 Source7Demo.java
Source7Demo.java:7: error: lambda expressions are not supported in -source 1.7
names.forEach(n -> System.out.println(n));
^
(use -source 8 or higher to enable lambda expressions)
这个报错只说明语法不兼容。如果把 Lambda 改回匿名类,仍可能因为调用了 8 才有的 API 而在 7 上运行失败;-source 不会帮你检查这一点。
-source/-target 改成 1.8;如果必须继续产出 7 的字节码,就不要使用 Lambda 和 default 方法,并显式设置 bootclasspath。PermGen → Metaspace:参数要清理03
PermGen 在 JDK 8 被移除,-XX:MaxPermSize 不再生效;JVM 会打印警告并继续启动。类元数据改放在 Metaspace,使用本地内存,不再受堆大小限制。
java -XX:MaxPermSize=256m -version
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
Metaspace 默认没有上限,受本机内存限制;可以用 -XX:MetaspaceSize 设置首次 GC 的高水位,用 -XX:MaxMetaspaceSize 设置硬上限。CompressedClassSpaceSize 限制压缩类指针区域。
MaxPermSize 留在启动脚本里,升到 9+ 会变成 Unrecognized VM option,JVM 直接无法启动。升级到 8 时就应删除,而不是等到下一次升级。二进制兼容与工具链04
default 方法让接口可以在不破坏实现类的情况下演进,这是 JDK 8 能向 Collection 添加 stream() 的前提。但二进制兼容有边界:新增抽象方法、父类同签名方法返回类型冲突、以及未更新字节码库,都可能出问题。
- ASM / CGLIB / Javassist 等字节码库必须能识别 class 52;旧版本会报
Unsupported class file major version 52。 - 编译期用
-source 1.8,运行期至少 8;混用 7 编译的依赖通常没问题。 - Nashorn 在 8 是正式引擎;JDK 11 弃用、15 移除,不要把新代码绑死在
nashorn上。 java.time与Date/Calendar可以互转,但java.sql.Date.toInstant()在 8 抛 UnsupportedOperationException。- IDE、Maven/Gradle、CI、容器镜像必须一起指向 8,否则本地能编译、线上仍可能跑在 7 上。
default 方法冲突的典型报错是 class C inherits unrelated defaults for m() from types A and B;必须在类里覆盖,并用 A.super.m() 或 B.super.m() 选择。
--add-opens;这些是 9+ 的问题。先把运行时和工具链升稳,再谈函数式改写。升级检查清单05
- 统一 JDK 与构建工具:IDE、Maven/Gradle、CI、容器镜像都指向 8。
- 删除
-XX:MaxPermSize、-XX:PermSize等 PermGen 参数,改用 Metaspace 参数。 - 升级 ASM/CGLIB/字节码插件,确认支持 class 52。
- 清理
-source 1.7/-target 1.7,至少改成 1.8。 - 跑一遍 default 方法冲突检查,尤其是实现多个接口的类。
- 先迁日期 API,再逐步用 Lambda/Stream 替换匿名类和循环。
迁移完成的标准不是「能启动」,而是「启动参数干净、依赖支持 class 52、测试通过、回滚路径明确」。
版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| class 文件版本 | 52 | JDK 7 是 51;JDK 9 是 53。 |
| PermGen | 8 移除 | JEP 122;元数据移到 Metaspace(本地内存)。 |
| `-XX:MaxPermSize` | 8 警告 / 9+ 失败 | 8 上打印 ignoring option 后继续启动;9+ 直接无法启动。 |
| default / static 接口方法 | 8 正式 | JEP 126;二进制兼容的接口演进手段。 |
| java.time | 8 正式 | JEP 150;不可变、线程安全。 |
| Nashorn | 8 正式 / 11 弃用 / 15 移除 | 不要把新代码绑定到 Nashorn。 |
| `--release` | 9 起 | JDK 8 只能用 -source/-target,无法检查 API 边界。 |
| `--add-opens` | 9 起 | JDK 8 没有模块系统,不需要该参数。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。