JDK 12 新特性详解
发布日期 :2019 年 3 月 19 日
规范版本 :Java SE 12(JSR 386)
是否 LTS :否
主题:Shenandoah GC 实验性登场、Switch 表达式首次预览、默认 CDS 归档
一、版本总览
JDK 12 是 6 个月发布节奏下的第二个特性版本,共集成 8 个 JEP。这个版本最值得关注的是:
- Shenandoah------Red Hat 主导的低停顿 GC,与 ZGC 并列成为两大低延迟 GC
- Switch 表达式首次预览 (JEP 325)------这是 JDK 14 正式落地的
switch新语法的起点 - JVM Constants API ------
invokedynamic生态的补完
二、核心特性详解
1. JEP 189:Shenandoah ------ 低停顿垃圾收集器(实验性)
背景:Red Hat 开发的低停顿 GC,与 ZGC 目标类似但实现路径不同。
bash
java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC MyApp
核心设计:
- 并发压缩(Concurrent Compaction):GC 与应用线程同时运行
- Brooks 转发指针(Brooks Forwarding Pointer):每个对象有一个额外的转发指针,指向其新位置
- 读写屏障:访问对象时通过转发指针定位真实对象
- 停顿时间与堆大小无关:停顿只取决于 GC Roots 数量
Shenandoah vs ZGC 对比:
| 维度 | Shenandoah | ZGC |
|---|---|---|
| 主导方 | Red Hat | Oracle |
| 指针技术 | Brooks 指针(对象内额外字段) | 染色指针(指针本身的位) |
| 堆内存开销 | 每对象多一个指针字(10~20% 开销) | 无额外对象开销,但需要虚拟地址空间 |
| 屏障类型 | Load + Store 屏障 | 主要是 Load 屏障 |
| 最大堆 | 数百 GB 级 | TB 级 |
| 压缩对象指针 | 支持 | 早期不支持 |
| 平台支持 | Linux/x64、AArch64 等 | 逐步扩展 |
| JDK 11 状态 | --- | 实验性 |
| JDK 12 状态 | 实验性 | 实验性 |
后续演进:
| 版本 | 改进 |
|---|---|
| JDK 15 (JEP 379) | 转正(不再是实验性) |
| JDK 24 (JEP 404) | 分代 Shenandoah(实验性) |
| JDK 25 (JEP 521) | 分代 Shenandoah 正式特性 |
2. JEP 325:Switch 表达式(预览)
这是 JDK 12 最具影响力的语言特性 。它把 switch 从「语句」升级为「表达式」,并引入箭头语法。
① 新语法:箭头标签(Arrow Labels)
java
// 旧语法(fall-through 陷阱)
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
default:
throw new IllegalArgumentException();
}
// JDK 12 新语法:无 fall-through
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
default -> throw new IllegalArgumentException();
}
关键差异:
| 特性 | 冒号标签 : |
箭头标签 -> |
|---|---|---|
| fall-through | 会穿透 | 不会穿透 |
| 多常量 | case A: case B: |
case A, B -> |
| 变量作用域 | 整个 switch 块 | 仅限该分支 |
| 是否需要 break | 需要(否则穿透) | 不需要 |
② switch 作为表达式
java
// 使用箭头 + yield
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
// 多语句分支需要 yield
int result = switch (mode) {
case "a" -> 1;
case "b" -> {
int temp = compute();
yield temp * 2; // yield 用于从块中返回值
}
default -> 0;
};
③ break 带值(旧语法中的表达式用法)
java
int result = switch (mode) {
case "a":
break 1; // JDK 12 预览中允许,JDK 13 起改为 yield
default:
break 0;
};
注意 :JDK 12 预览版中 break 用于返回值;JDK 13(JEP 354)改用 yield ,因为 break 在 switch 语句与 switch 表达式中语义混淆。
④ 穷尽性(Exhaustiveness)
当 switch 用作表达式时必须穷尽所有情况:
- 枚举:覆盖所有常量,或提供
default String/ 整型:必须有default
启用方式(JDK 12 是预览特性):
bash
javac --enable-preview --release 12 Example.java
java --enable-preview Example
3. JEP 334:JVM 常量 API
新增 java.lang.constant 包,提供描述类文件常量的名义描述符(nominal descriptors),让字节码生成库(ASM、Byte Buddy)不必再硬编码常量池结构。
核心接口与类:
| 类型 | 说明 |
|---|---|
Constable |
可表示为常量的类型的接口 |
ConstantDesc |
常量描述符的根接口 |
ClassDesc |
类/接口的类型描述符 |
MethodTypeDesc |
方法类型描述符 |
MethodHandleDesc |
方法句柄描述符 |
DirectMethodHandleDesc |
直接方法句柄描述符 |
DynamicConstantDesc |
动态常量描述符(配合 CONSTANT_Dynamic) |
ConstantDescs |
通用常量的工厂 |
使用示例:
java
import java.lang.constant.*;
// 描述一个类
ClassDesc listDesc = ClassDesc.of("java.util.List");
// 描述一个方法类型:(String, int) -> void
MethodTypeDesc mtd = MethodTypeDesc.of(
ConstantDescs.CD_void,
ConstantDescs.CD_String,
ConstantDescs.CD_int);
// 描述方法句柄
MethodHandleDesc mhd = MethodHandleDesc.ofMethod(
DirectMethodHandleDesc.Kind.STATIC,
ClassDesc.of("java.lang.Math"),
"abs",
MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int));
// 从 Class 对象获得描述符
ClassDesc desc = String.class.describeConstable().orElseThrow();
对普通开发者的意义:间接。它是字节码操作库与未来语言特性的基础设施。
4. JEP 230:Microbenchmark Suite
JDK 源码中引入基于 JMH(Java Microbenchmark Harness) 的微基准测试套件,位于 test/micro。
bash
# 在 JDK 源码中运行微基准
cd test/micro
make test TEST="micro:java.lang.String.*"
意义 :让 JVM 开发者能以标准、可重复的方式测量性能,避免「手写 System.nanoTime() 循环」的常见陷阱(死代码消除、JIT 预热不足等)。
JMH 示例(供参考,非 JDK 内部):
java
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
@Fork(3)
@State(Scope.Thread)
public class MyBenchmark {
private String str;
@Setup
public void setup() { str = "hello world"; }
@Benchmark
public int length() { return str.length(); }
@Benchmark
public String substring() { return str.substring(0, 5); }
}
5. JEP 341:默认 CDS 归档
背景 :JDK 10 的 AppCDS 需要手动两次运行才能生成归档。JDK 12 在构建 JDK 时自动生成默认归档,开箱即用。
bash
# 默认已启用,无需任何配置
java -Xshare:auto MyApp # auto 是默认值
# 查看 CDS 使用情况
java -Xshare:on -Xlog:cds MyApp
# 查看默认归档路径
java -Xshare:on -XX:+PrintSharedArchiveAndExit -version
核心类归档位置 :$JAVA_HOME/lib/server/classes.jsa
收益:JDK 自身约 400+ 个核心类免于解析与加载,启动时间提升约 5%~15%。
与 JDK 13 JEP 350(动态 CDS)的关系:
- JDK 12:默认归档仅含 JDK 核心类
- JDK 13:应用可在退出时自动生成包含自身类的归档
- JDK 14+:三层归档(默认 + 动态 + AppCDS)
6. JEP 340:统一 AArch64 移植(One AArch64 Port, Not Two)
背景:JDK 中曾同时存在两个 AArch64 移植:
aarch64(Oracle 主导,用于 Linux)arm64(Red Hat 等主导)
两者代码重复、维护成本高。
变更 :移除 arm64 移植,只保留 aarch64,同时把原 arm64 中的一些优化合并进来。
对用户的影响:无(构建产物一致)。
7. JEP 344:G1 可中止的混合收集(Abortable Mixed Collections)
问题 :G1 的混合 GC(Mixed GC)可能会选入过多老年代区域,导致单次停顿时间超出 -XX:MaxGCPauseMillis 目标。
改进 :如果预测到混合集合会超时,G1 会中止本次集合,只处理部分区域,把剩余工作留给下一次。
bash
# 控制相关行为
java -XX:MaxGCPauseMillis=200 \
-XX:G1MixedGCLiveThresholdPercent=85 \
-XX:G1MixedGCCountTarget=8 MyApp
收益:更可预测的停顿时间,减少「GC 停顿超目标」的情况。
8. JEP 346:G1 及时归还未使用内存
问题:G1 在堆收缩后不会立即把内存归还操作系统,导致容器环境中常驻内存(RSS)偏高。
改进 :G1 在空闲时(应用不活跃)自动把未使用内存归还 OS。
bash
# 相关参数
java -XX:G1PeriodicGCInterval=0 \ # 0 表示禁用周期 GC 触发(默认)
-XX:G1PeriodicGCSystemLoadThreshold=0 MyApp
触发条件:
- 堆使用率低于阈值
- 超过
G1PeriodicGCInterval毫秒没有发生 GC - 系统负载低于
G1PeriodicGCSystemLoadThreshold
收益:容器/云环境下内存占用显著降低,更适合按内存计费的场景。
三、JDK 12 特性速查表
| 类别 | JEP | 特性 | 状态 |
|---|---|---|---|
| 语言 | 325 | Switch 表达式 | 预览 |
| 库 | 334 | JVM 常量 API | 正式 |
| GC | 189 | Shenandoah 低停顿 GC | 实验性 |
| GC | 344 | G1 可中止混合收集 | 正式 |
| GC | 346 | G1 及时归还未使用内存 | 正式 |
| 运行时 | 341 | 默认 CDS 归档 | 正式 |
| 运行时 | 340 | 统一 AArch64 移植 | 正式 |
| 工具 | 230 | 微基准测试套件 | 正式 |
四、升级注意事项
- Switch 表达式是预览特性 ,需
--enable-preview,不应在生产代码中使用(预览特性无兼容性保证) - Shenandoah 是实验性的 ,需
-XX:+UnlockExperimentalVMOptions break value语法在 JDK 13 中改为yield------如果写过 JDK 12 预览代码,需要改写- 默认 CDS 归档与 JDK 版本绑定,升级 JDK 后自动重新生成
- G1 的内存归还行为变化:容器 RSS 监控指标会有变化,需重新校准阈值告警
五、参考资源
- 官方发布页:https://openjdk.org/projects/jdk/12/
- JEP 325(Switch 表达式):https://openjdk.org/jeps/325
- JEP 189(Shenandoah):https://openjdk.org/jeps/189
- JEP 334(JVM 常量 API):https://openjdk.org/jeps/334
- JEP 341(默认 CDS):https://openjdk.org/jeps/341