JDK 12 新特性详解

JDK 12 新特性详解

发布日期 :2019 年 3 月 19 日

规范版本 :Java SE 12(JSR 386)

是否 LTS :否

主题:Shenandoah GC 实验性登场、Switch 表达式首次预览、默认 CDS 归档


一、版本总览

JDK 12 是 6 个月发布节奏下的第二个特性版本,共集成 8 个 JEP。这个版本最值得关注的是:

  1. Shenandoah------Red Hat 主导的低停顿 GC,与 ZGC 并列成为两大低延迟 GC
  2. Switch 表达式首次预览 (JEP 325)------这是 JDK 14 正式落地的 switch 新语法的起点
  3. 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 微基准测试套件 正式

四、升级注意事项

  1. Switch 表达式是预览特性 ,需 --enable-preview,不应在生产代码中使用(预览特性无兼容性保证)
  2. Shenandoah 是实验性的 ,需 -XX:+UnlockExperimentalVMOptions
  3. break value 语法在 JDK 13 中改为 yield------如果写过 JDK 12 预览代码,需要改写
  4. 默认 CDS 归档与 JDK 版本绑定,升级 JDK 后自动重新生成
  5. G1 的内存归还行为变化:容器 RSS 监控指标会有变化,需重新校准阈值告警

五、参考资源


← JDK 11 | 返回索引 | JDK 13 →

相关推荐
用户094248568031 小时前
第28章:JDK容器感知、cgroup与云原生JVM参数治理
java·jvm
鬼手点金1 小时前
opencode-全能开发者配置
java·linux·服务器·前端·javascript·学习·前向传播
Nebula_g1 小时前
JavaSE拓展:可变参数
java·开发语言·算法·安全·javase·可变参数
念越1 小时前
初中语数英答题与竞赛平台
java·数据库·spring boot
知守观2 小时前
一个半天需求干了三天:代码腐化的五个信号与自查命令
java·后端·代码规范
代码山河2 小时前
Java学习路线图:2026年最新版,从入门到架构师
java·学习·架构·教程·面向对象·项目
anew___2 小时前
《从零手写操作系统 (30):环境变量与进程上下文——export/unset与继承语义》
java·开发语言·网络·jvm·算法
Cx330❀2 小时前
Qt 多线程深度解析:从底层原理到 UI 线程与同步实战
开发语言·qt·ui·搜索引擎·性能优化·图形渲染
jason成都2 小时前
rtklib_java项目新增模块:rtklib-research与rtklib-stream功能详解
java·gnss·rtklib·rtklib_java