JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · MIGRATION · 7 → 8

迁移到 JDK 8

JDK 8 · 速查主题 06 / 06 · 预计 20 分钟
进阶20 分钟JEP 122JEP 126
一句话从 7 升到 8 的难点不在语法,而在工具链与运行时假设:PermGen 参数、class 版本、字节码库、default 方法冲突。先把这些清理干净,再逐步引入 Lambda 与 Stream。

学完你要能自己回答

  1. JDK 8 的 class 版本、默认 GC 和内存模型有哪些变化?
  2. 为什么 -XX:MaxPermSize 在 8 上只是警告,在 9+ 却会让 JVM 无法启动?
  3. 升级到 8 时,哪些工具链和兼容性问题最容易被忽略?

先确认版本坐标01

JDK 8 的 class 文件版本是 52,默认 GC 是 Parallel(服务端模式),PermGen 已被 Metaspace 取代,Java EE 模块仍在 JDK 里。升级前先把源码、编译工具、运行时、依赖四者的版本对齐。

项目JDK 7JDK 8
class 版本5152
默认 GC(server)ParallelParallel
永久代PermGenMetaspace(本地内存)
日期 APIDate / Calendarjava.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() 选择。

升级到 8 不需要模块化,也不需要 --add-opens;这些是 9+ 的问题。先把运行时和工具链升稳,再谈函数式改写。

升级检查清单05

  1. 统一 JDK 与构建工具:IDE、Maven/Gradle、CI、容器镜像都指向 8。
  2. 删除 -XX:MaxPermSize、-XX:PermSize 等 PermGen 参数,改用 Metaspace 参数。
  3. 升级 ASM/CGLIB/字节码插件,确认支持 class 52。
  4. 清理 -source 1.7 / -target 1.7,至少改成 1.8。
  5. 跑一遍 default 方法冲突检查,尤其是实现多个接口的类。
  6. 先迁日期 API,再逐步用 Lambda/Stream 替换匿名类和循环。

迁移完成的标准不是「能启动」,而是「启动参数干净、依赖支持 class 52、测试通过、回滚路径明确」。

不要为了用 Lambda 而重写所有循环。Stream 适合表达「过滤、转换、聚合」;带复杂控制流、提前 break、多个副作用的循环,保留原样往往更清楚。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
class 文件版本52JDK 7 是 51;JDK 9 是 53。
PermGen8 移除JEP 122;元数据移到 Metaspace(本地内存)。
`-XX:MaxPermSize`8 警告 / 9+ 失败8 上打印 ignoring option 后继续启动;9+ 直接无法启动。
default / static 接口方法8 正式JEP 126;二进制兼容的接口演进手段。
java.time8 正式JEP 150;不可变、线程安全。
Nashorn8 正式 / 11 弃用 / 15 移除不要把新代码绑定到 Nashorn。
`--release`9 起JDK 8 只能用 -source/-target,无法检查 API 边界。
`--add-opens`9 起JDK 8 没有模块系统,不需要该参数。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. JDK 8 的 class 文件版本是多少?为什么升级时先看它?
52。class 版本不匹配是最常见的启动失败原因;运行期 JDK 版本低于编译期时,会抛 UnsupportedClassVersionError 并给出具体版本号。
02. 为什么 -XX:MaxPermSize 在 8 上不是致命错误?
因为 JDK 8 已移除 PermGen,但 HotSpot 仍识别这个旧参数,只是打印 ignoring option 警告后继续启动。到 JDK 9+,它变成无法识别的 VM 选项,JVM 会直接拒绝启动。
03. -source 1.7 能保证代码在 JDK 7 上运行吗?
不能。它只限制语法和字节码版本,不检查标准库 API;如果代码调用了 JDK 8 才有的方法,仍会在 7 上失败。真正的 API 边界需要 -bootclasspath 指向 7 的 rt.jar。
04. default 方法为什么能保持二进制兼容?
因为实现类不需要重新编译就能继承接口提供的默认实现;接口新增 default 方法不会强制实现类补方法。但新增抽象方法、或父类同签名方法返回类型冲突,仍可能破坏兼容性。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。