JDK 12 新特性详解
JDK 12 新特性详解
JDK 12 于 2019年3月19日 正式发布,是一个非 LTS(Non-LTS)版本。该版本引入了多个重要的语言特性预览(Switch 表达式、Pattern Matching),以及多项 JVM 和 GC 优化(G1 增强、ZGC 改进、Shenandoah GC 正式纳入)。JDK 12 为后续 LTS 版本(JDK 17)的多项核心特性奠定了基础。
目录
- Switch 表达式(Preview)
- Pattern Matching for instanceof(Preview)
- String 新方法:transform()
- NumberFormat 增强:紧凑数字格式
- Files.mismatch() 方法
- JVM Constants API
- Shenandoah GC(实验性)
- G1 GC 多项增强
- ZGC 未提交内存回收(实验性)
- 默认 CDS 归档
- Microbenchmark Suite
- AArch64 统一端口
- JDK 12 特性总览表
- 总结
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 344 | Abortable Mixed Collections | G1 可中止混合回收,避免超时 |
| JEP 346 | Return Unused Memory | G1 及时将未使用内存归还操作系统 |
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 | 类型 | 重要性 |
|---|---|---|---|---|
| 1 | Switch 表达式(Preview) | JEP 325 | 语言 | ★★★★★ |
| 2 | Pattern Matching for instanceof(Preview) | JEP 305 | 语言 | ★★★★★ |
| 3 | String.transform() | JEP 355 | API | ★★★☆☆ |
| 4 | NumberFormat 紧凑数字格式 | — | API | ★★★★☆ |
| 5 | Files.mismatch() | — | API | ★★★☆☆ |
| 6 | JVM Constants API | JEP 334 | API | ★★☆☆☆ |
| 7 | Shenandoah GC(实验性) | JEP 189 | JVM | ★★★★☆ |
| 8 | G1 可中止混合回收 | JEP 344 | JVM | ★★★☆☆ |
| 9 | G1 归还未使用内存 | JEP 346 | JVM | ★★★★☆ |
| 10 | ZGC 未提交内存回收 | — | JVM | ★★★☆☆ |
| 11 | 默认 CDS 归档 | JEP 341 | JVM | ★★★★☆ |
| 12 | Microbenchmark Suite | JEP 230 | 工具 | ★★☆☆☆ |
| 13 | AArch64 统一端口 | 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 语言现代化演进中的重要一环。
更多推荐


所有评论(0)