JDK 12 新特性详解

JDK 12 于 2019年3月19日 正式发布,是一个非 LTS(Non-LTS)版本。该版本引入了多个重要的语言特性预览(Switch 表达式、Pattern Matching),以及多项 JVM 和 GC 优化(G1 增强、ZGC 改进、Shenandoah GC 正式纳入)。JDK 12 为后续 LTS 版本(JDK 17)的多项核心特性奠定了基础。


目录

  1. Switch 表达式(Preview)
  2. Pattern Matching for instanceof(Preview)
  3. String 新方法:transform()
  4. NumberFormat 增强:紧凑数字格式
  5. Files.mismatch() 方法
  6. JVM Constants API
  7. Shenandoah GC(实验性)
  8. G1 GC 多项增强
  9. ZGC 未提交内存回收(实验性)
  10. 默认 CDS 归档
  11. Microbenchmark Suite
  12. AArch64 统一端口
  13. JDK 12 特性总览表
  14. 总结

1. Switch 表达式(Preview)

1.1 概述

JEP 325 引入了 Switch 表达式 作为预览特性。Switch 表达式支持箭头语法(->),无需 break 语句,并且可以作为表达式返回值。这是 Java 语言演进中最受期待的特性之一,最终在 JDK 14 正式定稿。

核心改进:

  • 箭头标签(->):无需 break,不会穿透(fall-through)
  • 作为表达式使用:可以返回值给变量
  • yield 关键字:在代码块中返回值

1.2 代码案例

// ============ 1. 传统 switch vs Switch 表达式 ============

// 传统 switch(需要 break,容易遗漏)
String day = "MONDAY";
String type;
switch (day) {
    case "MONDAY":
    case "TUESDAY":
    case "WEDNESDAY":
    case "THURSDAY":
    case "FRIDAY":
        type = "工作日";
        break;
    case "SATURDAY":
    case "SUNDAY":
        type = "周末";
        break;
    default:
        type = "未知";
}

// Switch 表达式(箭头语法,无需 break)
String type2 = switch (day) {
    case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "工作日";
    case "SATURDAY", "SUNDAY" -> "周末";
    default -> "未知";
};
System.out.println(type2); // "工作日"

// ============ 2. 多值匹配 ============

int numLetters = switch (day) {
    case "MONDAY", "FRIDAY", "SUNDAY" -> 6;
    case "TUESDAY"                     -> 7;
    case "THURSDAY", "SATURDAY"        -> 8;
    case "WEDNESDAY"                   -> 9;
    default -> throw new IllegalStateException("Invalid day: " + day);
};
System.out.println(numLetters); // 6

// ============ 3. yield 关键字(代码块返回值) ============

// 当 case 分支需要多行逻辑时,使用 yield 返回值
String result = switch (day) {
    case "MONDAY" -> {
        System.out.println("新的一周开始了");
        yield "工作日";
    }
    case "FRIDAY" -> {
        System.out.println("快到周末了");
        yield "工作日";
    }
    case "SATURDAY", "SUNDAY" -> {
        System.out.println("享受周末");
        yield "周末";
    }
    default -> {
        yield "未知";
    }
};

// ============ 4. 结合枚举使用 ============

enum Status {
    PENDING, APPROVED, REJECTED, COMPLETED
}

String describeStatus(Status status) {
    return switch (status) {
        case PENDING     -> "待审批";
        case APPROVED    -> "已批准";
        case REJECTED    -> "已拒绝";
        case COMPLETED   -> "已完成";
    };
    // 编译器知道所有枚举值已覆盖,无需 default
}

System.out.println(describeStatus(Status.APPROVED)); // "已批准"

// ============ 5. 结合模式匹配(Preview) ============

Object obj = "Hello JDK 12";
String description = switch (obj) {
    case Integer i    -> "整数: " + i;
    case String s     -> "字符串: " + s;
    case Long l       -> "长整数: " + l;
    case null         -> "空值";
    default           -> "未知类型: " + obj.getClass().getSimpleName();
};
System.out.println(description); // "字符串: Hello JDK 12"

// ============ 6. 在表达式中使用 ============

// 直接作为方法参数
System.out.println(switch (day) {
    case "MONDAY" -> "周一";
    case "TUESDAY" -> "周二";
    case "WEDNESDAY" -> "周三";
    case "THURSDAY" -> "周四";
    case "FRIDAY" -> "周五";
    default -> "其他";
});

// 用于三元表达式
int x = 5;
String parity = switch (x % 2) {
    case 0 -> "偶数";
    case 1 -> "奇数";
    default -> "未知";
};

// ============ 7. 传统 switch 的穿透问题对比 ============

// 传统 switch 容易遗漏 break 导致穿透
String fruit = "apple";
switch (fruit) {
    case "apple":
        System.out.println("苹果");
        // 忘记 break,会穿透到下一个 case
    case "banana":
        System.out.println("香蕉");
        break;
    case "orange":
        System.out.println("橙子");
        break;
}
// 输出:苹果 香蕉(穿透了!)

// Switch 表达式不会穿透
switch (fruit) {
    case "apple"  -> System.out.println("苹果");
    case "banana" -> System.out.println("香蕉");
    case "orange" -> System.out.println("橙子");
}
// 输出:苹果(不会穿透)

2. Pattern Matching for instanceof(Preview)

2.1 概述

JEP 305 引入了 instanceof 模式匹配 作为预览特性。当 instanceof 检测成功后,自动将对象转换为指定类型并绑定到一个新变量,省去显式类型转换。该特性最终在 JDK 16 正式定稿。

2.2 代码案例

// ============ 1. 传统 instanceof vs 模式匹配 ============

Object obj = "Hello JDK 12";

// 传统写法:先判断,再强制转换
if (obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.length());
}

// 模式匹配写法:判断的同时绑定变量
if (obj instanceof String str) {
    System.out.println(str.length()); // 直接使用 str
}

// ============ 2. 简化条件分支 ============

// 传统写法
if (obj instanceof Integer) {
    int value = (Integer) obj;
    System.out.println("Integer: " + value);
} else if (obj instanceof Long) {
    long value = (Long) obj;
    System.out.println("Long: " + value);
} else if (obj instanceof String) {
    String value = (String) obj;
    System.out.println("String: " + value);
} else if (obj == null) {
    System.out.println("null");
} else {
    System.out.println("Unknown: " + obj.getClass());
}

// 模式匹配写法
if (obj instanceof Integer i) {
    System.out.println("Integer: " + i);
} else if (obj instanceof Long l) {
    System.out.println("Long: " + l);
} else if (obj instanceof String s) {
    System.out.println("String: " + s);
} else if (obj == null) {
    System.out.println("null");
} else {
    System.out.println("Unknown: " + obj.getClass());
}

// ============ 3. 结合逻辑表达式 ============

// instanceof 模式匹配可以与 && 结合
// 变量只在条件为 true 时可用
if (obj instanceof String s && s.length() > 5) {
    System.out.println("长字符串: " + s);
}

// ============ 4. 在 equals 方法中使用 ============

class Point {
    private final int x;
    private final int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // 传统 equals
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Point)) return false;
        Point other = (Point) obj;
        return this.x == other.x && this.y == other.y;
    }

    // 模式匹配简化 equals
    public boolean equalsV2(Object obj) {
        if (this == obj) return true;
        if (obj instanceof Point other) {
            return this.x == other.x && this.y == other.y;
        }
        return false;
    }
}

// ============ 5. 处理集合中的混合类型 ============

List<Object> mixedList = List.of("Hello", 42, 3.14, "World", 100L);

for (Object item : mixedList) {
    if (item instanceof String s) {
        System.out.println("字符串: " + s.toUpperCase());
    } else if (item instanceof Integer i) {
        System.out.println("整数: " + (i * 2));
    } else if (item instanceof Double d) {
        System.out.println("浮点数: " + Math.round(d));
    } else if (item instanceof Long l) {
        System.out.println("长整数: " + l);
    }
}
// 输出:
// 字符串: HELLO
// 整数: 84
// 浮点数: 3
// 字符串: WORLD
// 长整数: 100

// ============ 6. 变量作用域 ============

// 模式变量的作用域:
// - 在 instanceof 判断为 true 的分支中可用
// - 在 else 分支中不可用

Object data = getData();

if (data instanceof String text) {
    // text 在此可用
    System.out.println(text.length());
} else {
    // text 在此不可用(编译错误)
    // System.out.println(text); // 编译错误!
}

// ============ 7. 嵌套模式匹配 ============

Object nested = List.of("Hello", "World");

if (nested instanceof List<?> list) {
    System.out.println("List size: " + list.size());
    for (Object item : list) {
        if (item instanceof String s) {
            System.out.println("  元素: " + s);
        }
    }
}

3. String 新方法:transform()

3.1 概述

JEP 355 新增了 String.transform(Function<String, R>) 方法,允许对字符串应用一个函数并返回结果。这使得字符串处理可以像 Stream 操作一样链式调用。

3.2 代码案例

// ============ 1. transform 基础用法 ============

// 链式字符串转换
String result = "Hello World"
    .transform(s -> s.trim())
    .transform(s -> s.toLowerCase())
    .transform(s -> s + "!");
System.out.println(result); // "hello world!"

// ============ 2. 与 Stream 操作对比 ============

// 传统写法
String input = "  hello world  ";
String trimmed = input.trim();
String upper = trimmed.toUpperCase();
String reversed = new StringBuilder(upper).reverse().toString();
System.out.println(reversed); // "DLROW OLLEH"

// transform 写法
String reversed2 = "  hello world  "
    .transform(String::trim)
    .transform(String::toUpperCase)
    .transform(s -> new StringBuilder(s).reverse().toString());
System.out.println(reversed2); // "DLROW OLLEH"

// ============ 3. 实际应用场景 ============

// 数据清洗管道
String rawInput = "  User@Example.COM  ";
String cleaned = rawInput
    .transform(String::trim)
    .transform(String::toLowerCase)
    .transform(s -> s.replaceAll("[^a-z@.]", ""));
System.out.println(cleaned); // "user@example.com"

// 格式化输出
String formatted = "42"
    .transform(Integer::parseInt)
    .transform(i -> i * i)
    .transform(i -> "平方值: " + i);
System.out.println(formatted); // "平方值: 1764"

// ============ 4. 结合自定义函数 ============

// 定义字符串处理函数
Function<String, String> censor = s -> {
    String[] badWords = {"bad", "ugly", "evil"};
    String result = s;
    for (String word : badWords) {
        result = result.replaceAll(word, "***");
    }
    return result;
};

String message = "This is a bad and ugly world, not evil at all";
String censored = message.transform(censor);
System.out.println(censored);
// "This is a *** and *** world, not *** at all"

// ============ 5. 与 Optional 结合 ============

// transform 返回 null 时的处理
String value = "  ";
String processed = value
    .transform(String::trim)
    .transform(s -> s.isEmpty() ? null : s)
    .transform(s -> s != null ? s.toUpperCase() : "DEFAULT");
System.out.println(processed); // "DEFAULT"

// ============ 6. 链式解析 ============

// 解析 CSV 行并转换
String csvLine = "Alice,25,Engineer";
List<String> fields = csvLine
    .transform(line -> line.split(","))
    .transform(Arrays::asList)
    .transform(list -> list.stream()
        .map(String::trim)
        .collect(Collectors.toList()));
System.out.println(fields); // ["Alice", "25", "Engineer"]

// ============ 7. 与 JDK 11 String 方法结合 ============

// 结合 JDK 11 的 lines()、strip()、repeat() 等方法
String multiline = "  hello  \n  world  \n  java  ";
String processed2 = multiline
    .transform(String::lines)           // JDK 11: 按行拆分
    .transform(lines -> lines
        .map(String::strip)             // JDK 11: 去除前后空白
        .filter(s -> !s.isEmpty())
        .map(String::toUpperCase)
        .collect(Collectors.joining(" | ")));
System.out.println(processed2); // "HELLO | WORLD | JAVA"

4. NumberFormat 增强:紧凑数字格式

4.1 概述

JDK 12 为 NumberFormat 新增了 紧凑数字格式化 支持(getCompactNumberInstance()),可以按照不同语言习惯将大数字格式化为简短形式,如 “1K”、“1M”、“1B” 等。

4.2 代码案例

// ============ 1. 基础紧凑格式化 ============

// 英文 Locale 下的紧凑格式
NumberFormat compactEn = NumberFormat.getCompactNumberInstance(
    Locale.ENGLISH, NumberFormat.Style.SHORT);
System.out.println(compactEn.format(1_000));         // "1K"
System.out.println(compactEn.format(10_000));        // "10K"
System.out.println(compactEn.format(1_000_000));     // "1M"
System.out.println(compactEn.format(1_000_000_000)); // "1B"
System.out.println(compactEn.format(1_500));         // "2K"(四舍五入)
System.out.println(compactEn.format(1_234_567));     // "1M"

// ============ 2. 不同 Locale 的紧凑格式 ============

// 中文 Locale
NumberFormat compactZh = NumberFormat.getCompactNumberInstance(
    Locale.CHINESE, NumberFormat.Style.SHORT);
System.out.println(compactZh.format(10_000));        // "1万"
System.out.println(compactZh.format(100_000_000));   // "1亿"

// 日文 Locale
NumberFormat compactJa = NumberFormat.getCompactNumberInstance(
    Locale.JAPANESE, NumberFormat.Style.SHORT);
System.out.println(compactJa.format(10_000));        // "1万"
System.out.println(compactJa.format(100_000_000));   // "1億"

// ============ 3. LONG 样式 ============

// SHORT 样式 vs LONG 样式
NumberFormat shortFmt = NumberFormat.getCompactNumberInstance(
    Locale.ENGLISH, NumberFormat.Style.SHORT);
NumberFormat longFmt = NumberFormat.getCompactNumberInstance(
    Locale.ENGLISH, NumberFormat.Style.LONG);

System.out.println(shortFmt.format(1_500));  // "2K"
System.out.println(longFmt.format(1_500));   // "2 thousand"

System.out.println(shortFmt.format(1_000_000)); // "1M"
System.out.println(longFmt.format(1_000_000));  // "1 million"

// ============ 4. 解析紧凑格式 ============

// 从紧凑格式解析回数字
NumberFormat parser = NumberFormat.getCompactNumberInstance(
    Locale.ENGLISH, NumberFormat.Style.SHORT);

try {
    Number parsed = parser.parse("1K");
    System.out.println(parsed); // 1000

    Number parsed2 = parser.parse("2M");
    System.out.println(parsed2); // 2000000
} catch (ParseException e) {
    e.printStackTrace();
}

// ============ 5. 实际应用场景 ============

// 社交媒体数据展示
long followers = 1_234_567;
long likes = 45_678;
long views = 8_900_000;

NumberFormat fmt = NumberFormat.getCompactNumberInstance(
    Locale.ENGLISH, NumberFormat.Style.SHORT);

System.out.println("粉丝: " + fmt.format(followers)); // "1M"
System.out.println("点赞: " + fmt.format(likes));      // "46K"
System.out.println("播放: " + fmt.format(views));      // "9M"

// ============ 6. 设置小数位数 ============

NumberFormat preciseFmt = NumberFormat.getCompactNumberInstance(
    Locale.ENGLISH, NumberFormat.Style.SHORT);
preciseFmt.setMinimumFractionDigits(1);
preciseFmt.setMaximumFractionDigits(2);

System.out.println(preciseFmt.format(1_500));     // "1.5K"
System.out.println(preciseFmt.format(1_234_567)); // "1.23M"

// ============ 7. 对比传统格式化 ============

long salary = 150_000;

// 传统方式
System.out.printf("传统: %,d%n", salary);         // "150,000"
System.out.printf("传统: %.1f万%n", salary / 10000.0); // "15.0万"

// 紧凑方式
System.out.println("紧凑(EN): " + compactEn.format(salary));  // "150K"
System.out.println("紧凑(ZH): " + compactZh.format(salary));  // "15万"

5. Files.mismatch() 方法

5.1 概述

JDK 12 在 java.nio.file.Files 中新增了 mismatch(Path, Path) 方法,用于查找两个文件的第一个不匹配字节的位置。如果文件内容完全相同,返回 -1L。

5.2 代码案例

// ============ 1. 基础用法 ============

Path file1 = Path.of("file1.txt");
Path file2 = Path.of("file2.txt");
Path file3 = Path.of("file3.txt");

// 创建测试文件
Files.writeString(file1, "Hello World");
Files.writeString(file2, "Hello Java!");
Files.writeString(file3, "Hello World");

// 比较文件
long mismatch1 = Files.mismatch(file1, file2);
System.out.println(mismatch1); // 6(第6个字节开始不同:'W' vs 'J')

long mismatch2 = Files.mismatch(file1, file3);
System.out.println(mismatch2); // -1(完全相同)

// ============ 2. 实际应用场景 ============

// 文件完整性校验
public boolean verifyFileIntegrity(Path original, Path backup) {
    try {
        long mismatchPos = Files.mismatch(original, backup);
        if (mismatchPos == -1) {
            System.out.println("文件完全一致");
            return true;
        } else {
            System.out.println("文件在第 " + mismatchPos + " 字节处不同");
            return false;
        }
    } catch (IOException e) {
        System.err.println("比较失败: " + e.getMessage());
        return false;
    }
}

// ============ 3. 批量文件比较 ============

// 比较两个目录中的同名文件
void compareDirectories(Path dir1, Path dir2) throws IOException {
    try (Stream<Path> paths = Files.list(dir1)) {
        paths.filter(Files::isRegularFile).forEach(file -> {
            Path other = dir2.resolve(file.getFileName());
            if (Files.exists(other)) {
                try {
                    long pos = Files.mismatch(file, other);
                    if (pos == -1) {
                        System.out.println(file.getFileName() + ": 相同");
                    } else {
                        System.out.println(file.getFileName() + ": 不同 (位置 " + pos + ")");
                    }
                } catch (IOException e) {
                    System.out.println(file.getFileName() + ": 比较失败");
                }
            } else {
                System.out.println(file.getFileName() + ": 仅存在于 dir1");
            }
        });
    }
}

// ============ 4. 大文件比较 ============

// Files.mismatch 是惰性读取的,适合大文件比较
// 它不会一次性加载整个文件到内存
Path largeFile1 = Path.of("large1.dat");
Path largeFile2 = Path.of("large2.dat");

long startTime = System.nanoTime();
long mismatchPos = Files.mismatch(largeFile1, largeFile2);
long elapsed = System.nanoTime() - startTime;

System.out.printf("比较耗时: %.2f ms%n", elapsed / 1_000_000.0);
if (mismatchPos == -1) {
    System.out.println("两个大文件完全相同");
} else {
    System.out.println("首次差异位置: " + mismatchPos);
}

// 清理测试文件
Files.deleteIfExists(file1);
Files.deleteIfExists(file2);
Files.deleteIfExists(file3);

6. JVM Constants API

6.1 概述

JEP 334 引入了 JVM Constants API,提供了一组标准类来描述 JVM 可加载的常量(loadable constants),包括方法句柄(MethodHandle)、方法类型(MethodType)等。这些类为工具链和框架提供了统一的常量描述方式。

6.2 代码案例

// ============ 1. 核心类概述 ============

// JDK 12 新增/增强的常量描述类:
// - ClassDesc       → 描述类引用(Class)
// - MethodTypeDesc  → 描述方法类型(MethodType)
// - MethodHandleDesc→ 描述方法句柄(MethodHandle)
// - DynamicConstantDesc → 描述动态常量(condy)

// ============ 2. ClassDesc ============

// 描述一个类引用
ClassDesc stringDesc = ClassDesc.of("java.lang.String");
System.out.println(stringDesc.displayName()); // "java.lang.String"

// 基本类型的 ClassDesc
ClassDesc intDesc = ClassDesc.ofDescriptor("I");   // int
ClassDesc voidDesc = ClassDesc.ofDescriptor("V");   // void
ClassDesc arrayDesc = ClassDesc.ofDescriptor("[Ljava.lang.String;"); // String[]

// 数组类型
ClassDesc stringArrayDesc = ClassDesc.of("java.lang.String").arrayType();
System.out.println(stringArrayDesc.descriptorString()); // "[Ljava.lang.String;"

// ============ 3. MethodTypeDesc ============

// 描述方法签名:(String, int) -> boolean
MethodTypeDesc mtd = MethodTypeDesc.of(
    ClassDesc.ofDescriptor("Z"),           // 返回 boolean
    ClassDesc.of("java.lang.String"),      // 参数1: String
    ClassDesc.ofDescriptor("I")            // 参数2: int
);
System.out.println(mtd); // "(Ljava/lang/String;I)Z"

// 无参无返回值方法
MethodTypeDesc noArgsVoid = MethodTypeDesc.of(
    ClassDesc.ofDescriptor("V")
);
System.out.println(noArgsVoid); // "()V"

// ============ 4. MethodHandleDesc ============

// 描述一个方法句柄
// 描述 System.out.println(String) 的方法句柄
MethodHandleDesc printlnDesc = MethodHandleDesc.of(
    ConstantDescs.BSM_INVOKE,            // bootstrap method
    MethodTypeDesc.of(
        ClassDesc.ofDescriptor("V"),
        ClassDesc.of("java.lang.String")
    ),
    // ... 具体描述方法句柄的引用
);

// ============ 5. 实际应用场景 ============

// 用于字节码生成工具(如 ASM、ByteBuddy)
// 统一描述常量,避免直接使用字符串描述符

// 构建方法签名
class MethodSignature {
    static MethodTypeDesc of(String returnType, String... paramTypes) {
        ClassDesc retDesc = ClassDesc.ofDescriptor(returnType);
        ClassDesc[] paramDescs = Arrays.stream(paramTypes)
            .map(ClassDesc::of)
            .toArray(ClassDesc[]::new);
        return MethodTypeDesc.of(retDesc, paramDescs);
    }
}

// 描述 String.valueOf(Object) -> String
MethodTypeDesc valueOfDesc = MethodSignature.of(
    "Ljava/lang/String;",     // 返回 String
    "java.lang.Object"        // 参数 Object
);
System.out.println(valueOfDesc); // "(Ljava/lang/Object;)Ljava/lang/String;"

// ============ 6. 与旧方式对比 ============

// 旧方式:直接使用字符串描述符(易出错)
String oldDescriptor = "(Ljava/lang/String;I)Z";

// 新方式:使用类型安全的 API
MethodTypeDesc newDescriptor = MethodTypeDesc.of(
    ConstantDescs.CD_boolean,              // boolean
    ConstantDescs.CD_String,               // String
    ConstantDescs.CD_int                   // int
);
System.out.println(newDescriptor.descriptorString()); // "(Ljava/lang/String;I)Z"
// 编译时类型安全,避免字符串拼写错误

7. Shenandoah GC(实验性)

7.1 概述

JEP 189 将 Shenandoah GC 纳入 JDK 12。Shenandoah 是一个低暂停(low-pause)垃圾收集器,其暂停时间与堆大小无关(与 G1 不同,G1 的暂停时间随堆增大而增大)。Shenandoah 通过 转发表(Forwarding Table) 和 Brooks Pointer 实现并发压缩。

核心特点:

  • 并发标记(Concurrent Marking)
  • 并发回收(Concurrent Evacuation)
  • 暂停时间与堆大小无关
  • 适合大堆(数十 GB 到 TB 级)

7.2 代码案例

# ============ 1. 启用 Shenandoah GC ============

# JDK 12 中 Shenandoah 是实验性的,需要额外参数
java -XX:+UnlockExperimentalVMOptions \
     -XX:+UseShenandoahGC \
     -jar myapp.jar

# ============ 2. 常用 JVM 参数 ============

# 基本启用
java -XX:+UnlockExperimentalVMOptions \
     -XX:+UseShenandoahGC \
     -Xms4g -Xmx4g \
     -jar myapp.jar

# 启用 GC 日志
java -XX:+UnlockExperimentalVMOptions \
     -XX:+UseShenandoahGC \
     -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -Xms4g -Xmx4g \
     -jar myapp.jar

# ============ 3. 与 G1 对比测试 ============

# G1 GC
java -XX:+UseG1GC \
     -Xms8g -Xmx8g \
     -XX:MaxGCPauseMillis=200 \
     -Xlog:gc*:file=g1-gc.log \
     -jar myapp.jar

# Shenandoah GC
java -XX:+UnlockExperimentalVMOptions \
     -XX:+UseShenandoahGC \
     -Xms8g -Xmx8g \
     -Xlog:gc*:file=shenandoah-gc.log \
     -jar myapp.jar

# 对比指标:
# - 最大暂停时间(Max Pause Time)
# - GC 总吞吐量(Throughput)
# - 内存占用(Memory Footprint)
// ============ 4. GC 压力测试 ============

// 用于测试不同 GC 的性能
public class GCStressTest {
    private static final int OBJECT_COUNT = 1_000_000;
    private static final List<byte[]> survivors = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            // 分配大量临时对象
            for (int j = 0; j < OBJECT_COUNT; j++) {
                byte[] data = new byte[1024]; // 1KB
                // 部分对象保留引用(模拟长期存活)
                if (j % 100 == 0) {
                    survivors.add(data);
                }
            }
            // 清理保留的对象
            survivors.clear();
            System.out.printf("Round %d: heap used = %d MB%n",
                i, Runtime.getRuntime().totalMemory() / 1024 / 1024);
        }

        long elapsed = System.currentTimeMillis() - startTime;
        System.out.printf("Total time: %d ms%n", elapsed);
    }
}

8. G1 GC 多项增强

8.1 概述

JDK 12 对 G1 GC 进行了多项重要增强:

JEP特性说明
JEP 344Abortable Mixed CollectionsG1 可中止混合回收,避免超时
JEP 346Return Unused MemoryG1 及时将未使用内存归还操作系统

8.2 Abortable Mixed Collections(JEP 344)

# ============ 1. 可中止的混合回收 ============

# G1 将混合回收(Mixed Collection)拆分为多个阶段
# 当回收收益低于阈值时,自动中止剩余阶段
# 避免不必要的长时间暂停

# 默认启用(JDK 12+)
java -XX:+UseG1GC \
     -XX:G1MixedGCCountTarget=8 \
     -Xlog:gc+phases=debug \
     -jar myapp.jar

# 关键参数:
# -XX:G1MixedGCCountTarget=N  → 目标混合回收次数
# -XX:G1HeapWastePercent=N    → 可容忍的堆浪费百分比

8.3 Return Unused Committed Memory(JEP 346)

# ============ 2. 及时归还未使用内存 ============

# G1 在空闲时将未使用的堆内存归还给操作系统
# 特别适合容器化部署(Docker/K8s)

java -XX:+UseG1GC \
     -XX:G1PeriodicGCInterval=1000 \
     -XX:G1PeriodicGCSystemLoadThreshold=0.7 \
     -XX:G1PeriodicGCInterval=5000 \
     -Xlog:gc*=info \
     -jar myapp.jar

# 关键参数:
# -XX:G1PeriodicGCInterval=N          → 周期性 GC 间隔(ms)
# -XX:G1PeriodicGCSystemLoadThreshold → 系统负载阈值
# -XX:G1PeriodicGCInterval            → 检查间隔

# ============ 3. 容器环境配置 ============

# Docker 中限制内存并启用自动归还
# docker run --memory=2g --memory-swap=2g \
java -XX:+UseG1GC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:G1PeriodicGCInterval=5000 \
     -XX:G1PeriodicGCSystemLoadThreshold=0.5 \
     -jar myapp.jar
// ============ 4. 监控内存归还 ============

// 通过 JMX 监控 G1 内存归还情况
import java.lang.management.*;
import com.sun.management.*;

public class G1MemoryMonitor {
    public static void main(String[] args) {
        HotSpotDiagnosticMXBean diagnostic =
            ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);

        // 定期打印堆内存信息
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(() -> {
            MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
            MemoryUsage heapUsage = memBean.getHeapMemoryUsage();

            System.out.printf("Heap: used=%dMB, committed=%dMB, max=%dMB%n",
                heapUsage.getUsed() / 1024 / 1024,
                heapUsage.getCommitted() / 1024 / 1024,
                heapUsage.getMax() / 1024 / 1024);

            // committed < max 说明内存已归还给 OS
            long returnedMB = (heapUsage.getMax() - heapUsage.getCommitted()) / 1024 / 1024;
            System.out.printf("已归还 OS: %dMB%n", returnedMB);
        }, 0, 5, TimeUnit.SECONDS);
    }
}

9. ZGC 未提交内存回收(实验性)

9.1 概述

JDK 12 增强了 ZGC(JDK 11 引入的实验性 GC),支持将未使用的堆内存归还给操作系统。ZGC 在 JDK 11 中只支持并发标记和回收,但不归还内存。JDK 12 补全了这一能力。

9.2 代码案例

# ============ 1. 启用 ZGC(JDK 12 实验性) ============

java -XX:+UnlockExperimentalVMOptions \
     -XX:+UseZGC \
     -Xms4g -Xmx16g \
     -Xlog:gc*:file=zgc-gc.log \
     -jar myapp.jar

# ============ 2. 与 G1 的内存归还对比 ============

# G1(JDK 12 新增归还能力)
java -XX:+UseG1GC \
     -XX:G1PeriodicGCInterval=5000 \
     -Xms2g -Xmx8g \
     -jar app-g1.jar

# ZGC(JDK 12 增强归还能力)
java -XX:+UnlockExperimentalVMOptions \
     -XX:+UseZGC \
     -Xms2g -Xmx8g \
     -jar app-zgc.jar

# 观察指标:
# - RSS(Resident Set Size)变化
# - 容器内存使用率
# - GC 暂停时间
// ============ 3. 内存归还监控 ============

// 监控 ZGC 内存归还行为
public class ZGCMemoryMonitor {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();

        // 分配大量内存
        List<byte[]> buffers = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            buffers.add(new byte[1024 * 1024]); // 1MB each
        }

        System.out.printf("分配后 - 空闲: %dMB, 总计: %dMB%n",
            runtime.freeMemory() / 1024 / 1024,
            runtime.totalMemory() / 1024 / 1024);

        // 释放引用
        buffers.clear();
        buffers = null;

        // 触发 GC
        System.gc();
        Thread.sleep(2000);

        System.out.printf("释放后 - 空闲: %dMB, 总计: %dMB%n",
            runtime.freeMemory() / 1024 / 1024,
            runtime.totalMemory() / 1024 / 1024);
        // ZGC 会将 totalMemory 降低(归还给 OS)
    }
}

10. 默认 CDS 归档

10.1 概述

JEP 341 改进了 Class Data Sharing(CDS),在 JDK 构建时自动生成默认的 CDS 归档(classes.jsa)。用户无需手动运行 java -Xshare:dump 即可受益。CDS 可以显著缩短应用启动时间。

10.2 代码案例

# ============ 1. JDK 12 之前:手动创建 CDS 归档 ============

# Step 1: 生成类列表
java -XX:DumpLoadedClassList=classes.lst -jar myapp.jar

# Step 2: 创建共享归档
java -Xshare:dump -XX:SharedClassListFile=classes.lst \
     -XX:SharedArchiveFile=classes.jsa

# Step 3: 使用共享归档启动
java -Xshare:on -XX:SharedArchiveFile=classes.jsa -jar myapp.jar

# ============ 2. JDK 12:自动使用默认 CDS 归档 ============

# 无需任何额外步骤,JDK 自带默认归档
# 直接启动即可获得启动加速
java -jar myapp.jar

# 验证 CDS 是否生效
java -Xlog:class+load -jar myapp.jar 2>&1 | grep "shared"
# 输出中会看到 "shared objects" 相关信息

# ============ 3. 自定义 CDS 归档(动态 CDS) ============

# JDK 12 支持在应用退出时自动生成 CDS 归档
# 使用 -XX:ArchiveClassesAtExit 参数

# Step 1: 运行应用并生成归档
java -XX:ArchiveClassesAtExit=myapp-cds.jsa -jar myapp.jar

# Step 2: 使用自定义归档启动
java -XX:SharedArchiveFile=myapp-cds.jsa -jar myapp.jar

# ============ 4. 启动时间对比 ============

# 无 CDS
time java -jar myapp.jar --help
# real: 0.350s

# 默认 CDS(JDK 12 自动)
time java -jar myapp.jar --help
# real: 0.280s(提升约 20%)

# 自定义 CDS
time java -XX:SharedArchiveFile=myapp-cds.jsa -jar myapp.jar --help
# real: 0.220s(提升约 37%)

# ============ 5. 查看 CDS 信息 ============

# 查看默认归档位置
java -Xshare:dump -Xlog:cds=info 2>&1 | grep "jsa"
# 通常位于 $JAVA_HOME/lib/server/classes.jsa

# 检查 CDS 状态
java -XshowSettings:all 2>&1 | grep -i "shared"

11. Microbenchmark Suite

11.1 概述

JEP 230 在 JDK 源码中新增了一套 微基准测试套件,用于测试 JVM 和 JDK 库的性能。这些测试基于 JMH(Java Microbenchmark Harness)框架,帮助开发者发现性能回归。

11.2 代码案例

// ============ 1. 使用 JMH 编写微基准测试 ============

// JMH 是 JDK 微基准测试套件的基础
// 添加 JMH 依赖(Maven)
/*
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.37</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.37</version>
</dependency>
*/

// ============ 2. 基准测试示例 ============

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(3)
@State(Scope.Thread)
public class StringBenchmark {

    private String testString;

    @Setup
    public void setup() {
        testString = "Hello, JDK 12 Benchmark!";
    }

    // 测试 String.transform()
    @Benchmark
    public String testTransform() {
        return testString
            .transform(String::trim)
            .transform(String::toUpperCase);
    }

    // 对比传统写法
    @Benchmark
    public String testTraditional() {
        return testString.trim().toUpperCase();
    }

    // 测试 String.strip() vs String.trim()
    @Benchmark
    public String testStrip() {
        return testString.strip();
    }

    @Benchmark
    public String testTrim() {
        return testString.trim();
    }
}

// 运行基准测试
// java -jar benchmarks.jar StringBenchmark

// ============ 3. 集合操作基准测试 ============

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
@State(Scope.Thread)
public class CollectionBenchmark {

    private List<Integer> numbers;

    @Setup
    public void setup() {
        numbers = IntStream.range(0, 10_000)
            .boxed()
            .collect(Collectors.toList());
    }

    // Stream 并行求和
    @Benchmark
    public long parallelSum() {
        return numbers.parallelStream()
            .mapToLong(Integer::longValue)
            .sum();
    }

    // Stream 串行求和
    @Benchmark
    public long serialSum() {
        return numbers.stream()
            .mapToLong(Integer::longValue)
            .sum();
    }

    // 传统循环求和
    @Benchmark
    public long loopSum() {
        long sum = 0;
        for (int n : numbers) {
            sum += n;
        }
        return sum;
    }
}

// ============ 4. GC 性能基准测试 ============

@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 2, time = 2)
@Measurement(iterations = 5, time = 2)
@Fork(2)
@State(Scope.Thread)
public class GCBenchmark {

    // 测试不同对象大小对 GC 的影响
    @Benchmark
    public List<byte[]> allocateSmall() {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(new byte[64]); // 64 bytes
        }
        return list;
    }

    @Benchmark
    public List<byte[]> allocateMedium() {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(new byte[4096]); // 4KB
        }
        return list;
    }

    @Benchmark
    public List<byte[]> allocateLarge() {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(new byte[1024 * 1024]); // 1MB
        }
        return list;
    }
}

12. AArch64 统一端口

12.1 概述

JEP 341 移除了 JDK 中的 arm64 端口(aarch64 的旧实现),只保留一个统一的 AArch64 端口。此前 JDK 维护了两个 AArch64 实现:aarch64(由 Red Hat/ARM 维护)和 arm64(由 Oracle 维护)。JDK 12 统一为 aarch64 端口。

影响:

  • 减少维护成本
  • 统一代码路径
  • 更好的 AArch64 性能

12.2 代码案例

# ============ 1. 构建 JDK 12(AArch64 平台) ============

# 在 AArch64(ARM64)平台上构建 JDK 12
# 只使用统一的 aarch64 端口

# 配置构建
bash configure \
    --openjdk-target=aarch64-linux-gnu \
    --with-boot-jdk=/path/to/boot-jdk \
    --with-extra-cxxflags="-march=armv8-a" \
    --with-extra-cflags="-march=armv8-a"

# 构建
make images

# ============ 2. 验证 AArch64 端口 ============

# 在 ARM64 服务器上运行
java -version
# openjdk version "12" 2019-03-19

# 查看 JVM 特性
java -XX:+PrintFlagsFinal -version 2>/dev/null | grep -i "aarch64"

# ============ 3. 性能对比 ============

# 在 AArch64 平台上测试性能
# JDK 11(旧 arm64 端口)vs JDK 12(统一 aarch64 端口)

# 基准测试
java -jar dacapo-9.12-bach.jar avrora
java -jar dacapo-9.12-bach.jar fop
java -jar dacapo-9.12-bach.jar lusearch

# 预期结果:
# - JDK 12 在 AArch64 上性能提升 5-10%
# - 内存占用降低
# - 启动时间缩短
// ============ 4. AArch64 特性检测 ============

// 检测当前平台的 CPU 特性
public class AArch64FeatureDetection {
    public static void main(String[] args) {
        String arch = System.getProperty("os.arch");
        System.out.println("Architecture: " + arch);

        if (arch.equals("aarch64")) {
            System.out.println("Running on AArch64 platform");

            // 检测 JVM 优化的 AArch64 特性
            String vmName = System.getProperty("java.vm.name");
            String vmVersion = System.getProperty("java.vm.version");
            System.out.println("JVM: " + vmName + " " + vmVersion);

            // 在 AArch64 上,JDK 12 会自动启用:
            // - NEON 指令集优化
            // - 硬件 CRC32 计算
            // - 优化的内存屏障
        }
    }
}

13. JDK 12 特性总览表

序号特性JEP类型重要性
1Switch 表达式(Preview)JEP 325语言★★★★★
2Pattern Matching for instanceof(Preview)JEP 305语言★★★★★
3String.transform()JEP 355API★★★☆☆
4NumberFormat 紧凑数字格式—API★★★★☆
5Files.mismatch()—API★★★☆☆
6JVM Constants APIJEP 334API★★☆☆☆
7Shenandoah GC(实验性)JEP 189JVM★★★★☆
8G1 可中止混合回收JEP 344JVM★★★☆☆
9G1 归还未使用内存JEP 346JVM★★★★☆
10ZGC 未提交内存回收—JVM★★★☆☆
11默认 CDS 归档JEP 341JVM★★★★☆
12Microbenchmark SuiteJEP 230工具★★☆☆☆
13AArch64 统一端口JEP 341平台★★☆☆☆

14. 总结

JDK 12 虽然是非 LTS 版本,但为 Java 平台引入了多项影响深远的特性:

语言演进:

  • Switch 表达式(Preview)引入了箭头语法和 yield 关键字,彻底改变了 switch 的使用方式,最终在 JDK 14 正式定稿
  • Pattern Matching for instanceof(Preview)简化了类型判断和转换的代码,最终在 JDK 16 正式定稿

API 增强:

  • String.transform() 支持链式字符串转换
  • NumberFormat.getCompactNumberInstance() 支持紧凑数字格式化(1K、1M、1万、1亿)
  • Files.mismatch() 提供高效的文件差异比较

JVM 与 GC 优化:

  • Shenandoah GC 正式纳入,提供与堆大小无关的低暂停 GC
  • G1 GC 支持可中止混合回收和内存归还,更适合容器化部署
  • ZGC 增强内存归还能力
  • 默认 CDS 归档 自动加速应用启动

工具链:

  • JVM Constants API 为字节码工具提供类型安全的常量描述
  • Microbenchmark Suite 帮助发现性能回归
  • AArch64 统一端口降低维护成本

JDK 12 的多项预览特性为后续 LTS 版本(特别是 JDK 17)奠定了基础,是 Java 语言现代化演进中的重要一环。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐