JDK 14 新特性详解

JDK 14 于 2020年3月17日 正式发布,是 Java 半年发布节奏的第四个非 LTS 版本。JDK 14 引入了多个重磅特性:Records(记录类) 预览、Pattern Matching for instanceof 预览、Switch 表达式正式标准化、Helpful NullPointerExceptions(更友好的空指针异常信息)、JFR 事件流 等,是继 JDK 12/13 之后又一个特性丰富的版本。


目录

  1. Records 记录类(预览)
  2. Pattern Matching for instanceof(预览)
  3. Switch 表达式正式标准化
  4. Text Blocks 文本块(第二次预览)
  5. Helpful NullPointerExceptions 空指针异常增强
  6. Foreign Memory Access API(孵化)
  7. JFR 事件流
  8. Packaging Tool 打包工具(孵化)
  9. ZGC 跨平台支持
  10. CMS GC 移除
  11. 其他重要变更
  12. JDK 14 特性总览表

1. Records 记录类(预览)

1.1 概述

JEP 395 — Records(记录类)是 JDK 14 最重要的语言特性,以预览形式引入。Records 提供了一种紧凑的语法来声明不可变数据载体类(data carrier classes),编译器自动生成构造器、getter、equals()、hashCode()、toString() 等方法。

核心特点:

  • 不可变(所有字段隐式 final)
  • 自动生成构造器、访问器(注意:不是 getXxx(),而是 xxx())、equals()、hashCode()、toString()
  • 不能继承其他类(隐式继承 java.lang.Record)
  • 可以实现接口
  • 可以声明静态字段和方法
  • 可以声明实例方法(覆盖自动生成的方法)

1.2 代码案例

// ============ 1. 基础 Record 定义 ============

// 传统写法(需要大量样板代码)
public final class Person {
    private final String name;
    private final int age;
    private final String email;

    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public String name() { return name; }
    public int age() { return age; }
    public String email() { return email; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return age == person.age &&
               Objects.equals(name, person.name) &&
               Objects.equals(email, person.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, email);
    }

    @Override
    public String toString() {
        return "Person[name=" + name + ", age=" + age + ", email=" + email + "]";
    }
}

// Record 写法(一行搞定)
public record PersonRecord(String name, int age, String email) {}

// 使用
PersonRecord person = new PersonRecord("Alice", 25, "alice@example.com");
System.out.println(person.name());   // "Alice"(注意:不是 getName())
System.out.println(person.age());    // 25
System.out.println(person.email());  // "alice@example.com"
System.out.println(person);          // PersonRecord[name=Alice, age=25, email=alice@example.com]

// ============ 2. equals 和 hashCode 自动实现 ============

PersonRecord p1 = new PersonRecord("Alice", 25, "alice@example.com");
PersonRecord p2 = new PersonRecord("Alice", 25, "alice@example.com");
PersonRecord p3 = new PersonRecord("Bob", 30, "bob@example.com");

System.out.println(p1.equals(p2)); // true(值相等)
System.out.println(p1.equals(p3)); // false
System.out.println(p1.hashCode() == p2.hashCode()); // true

// ============ 3. 紧凑构造器(Compact Constructor) ============

// 可以在紧凑构造器中添加参数校验
public record Range(int min, int max) {
    // 紧凑构造器(无参数列表)
    public Range {
        if (min > max) {
            throw new IllegalArgumentException("min > max: " + min + " > " + max);
        }
    }
}

Range valid = new Range(1, 10);      // OK
Range invalid = new Range(10, 1);    // 抛出 IllegalArgumentException

// ============ 4. 自定义方法 ============

public record Point(double x, double y) {
    // 自定义实例方法
    public double distanceTo(Point other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    public double distanceToOrigin() {
        return Math.sqrt(x * x + y * y);
    }

    // 覆盖自动生成的 toString
    @Override
    public String toString() {
        return String.format("(%f, %f)", x, y);
    }
}

Point p = new Point(3.0, 4.0);
System.out.println(p.distanceToOrigin()); // 5.0
System.out.println(p.distanceTo(new Point(6.0, 8.0))); // 5.0

// ============ 5. 静态工厂方法 ============

public record Money(BigDecimal amount, Currency currency) {
    // 静态工厂方法
    public static Money of(double amount, String currencyCode) {
        return new Money(BigDecimal.valueOf(amount), Currency.getInstance(currencyCode));
    }

    public static Money zero(String currencyCode) {
        return new Money(BigDecimal.ZERO, Currency.getInstance(currencyCode));
    }

    // 静态方法
    public static Money max(Money a, Money b) {
        if (!a.currency().equals(b.currency())) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return a.amount().compareTo(b.amount()) >= 0 ? a : b;
    }
}

Money m = Money.of(99.99, "CNY");
System.out.println(m); // Money[amount=99.99, currency=CNY]

// ============ 6. 实现接口 ============

public interface Printable {
    String toFormattedString();
}

public record User(String name, String email, int age) implements Printable {
    @Override
    public String toFormattedString() {
        return String.format("用户名: %s, 邮箱: %s, 年龄: %d", name, email, age);
    }
}

User user = new User("Alice", "alice@example.com", 25);
System.out.println(user.toFormattedString());
// "用户名: Alice, 邮箱: alice@example.com, 年龄: 25"

// ============ 7. Record 与集合配合 ============

List<PersonRecord> people = List.of(
    new PersonRecord("Alice", 25, "alice@example.com"),
    new PersonRecord("Bob", 30, "bob@example.com"),
    new PersonRecord("Charlie", 25, "charlie@example.com")
);

// 按年龄分组
Map<Integer, List<PersonRecord>> byAge = people.stream()
    .collect(Collectors.groupingBy(PersonRecord::age));
// {25=[PersonRecord[name=Alice,...], PersonRecord[name=Charlie,...]],
//  30=[PersonRecord[name=Bob,...]]}

// 作为 Map 的 key(因为 hashCode/equals 已实现)
Map<PersonRecord, String> personRoles = new HashMap<>();
personRoles.put(p1, "管理员");
personRoles.put(p2, "管理员"); // p1.equals(p2),所以会覆盖
System.out.println(personRoles.size()); // 1

// ============ 8. 嵌套 Record ============

public record Address(String city, String street, String zipCode) {}

public record Company(String name, Address address) {}

Company company = new Company(
    "Tech Corp",
    new Address("Beijing", "Zhongguancun Street", "100080")
);

System.out.println(company.address().city()); // "Beijing"

// ============ 9. Record 与序列化 ============

// Record 实现了 java.io.Serializable(如果组件类型都可序列化)
// 序列化格式与组件类型一致

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

// DTO(数据传输对象)
public record UserDTO(Long id, String username, String email, LocalDateTime createdAt) {}

// 数据库查询结果映射
public record OrderSummary(Long orderId, String customerName, BigDecimal total, String status) {}

// 配置对象
public record DatabaseConfig(String url, String username, String password, int maxPoolSize) {}

// API 响应
public record ApiResponse<T>(int code, String message, T data) {
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data);
    }

    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

ApiResponse<String> response = ApiResponse.success("Hello");
System.out.println(response); // ApiResponse[code=200, message=success, data=Hello]

1.3 编译与运行

# Record 是预览特性
javac --enable-preview --release 14 App.java
java --enable-preview -cp . App

2. Pattern Matching for instanceof(预览)

2.1 概述

JEP 305 — Pattern Matching for instanceof(instanceof 模式匹配)以预览形式引入。传统的 instanceof 检查后需要显式类型转换,模式匹配可以在类型检查的同时自动绑定变量,省去冗余的类型转换代码。

2.2 代码案例

// ============ 1. 传统写法 vs 模式匹配 ============

// 传统写法
Object obj = "Hello World";
if (obj instanceof String) {
    String str = (String) obj; // 显式类型转换
    System.out.println(str.length());
}

// 模式匹配写法(JDK 14)
if (obj instanceof String str) { // 类型检查 + 变量绑定一步完成
    System.out.println(str.length()); // 直接使用 str
}

// ============ 2. 结合逻辑运算 ============

// 模式变量在条件表达式中可用
Object obj2 = "Hello";

// && 组合
if (obj2 instanceof String str && str.length() > 3) {
    System.out.println("字符串长度大于3: " + str);
}

// 注意:|| 组合时,模式变量在右侧不可用
// if (obj instanceof String str || str.length() > 3) // 编译错误!

// ============ 3. 取反场景 ============

// 传统写法
if (!(obj instanceof String str)) {
    // str 在这里不可用
    throw new IllegalArgumentException("Not a string");
}
// str 在这里可用(JDK 14 预览)
System.out.println(str.length());

// ============ 4. 多重类型判断 ============

Object value = 42;

// 传统写法
if (value instanceof Integer) {
    int i = (Integer) value;
    System.out.println("Integer: " + i);
} else if (value instanceof Long) {
    long l = (Long) value;
    System.out.println("Long: " + l);
} else if (value instanceof String) {
    String s = (String) value;
    System.out.println("String: " + s);
}

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

// ============ 5. equals 方法优化 ============

// 传统 equals
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Person)) return false; // 传统:需要后续强转
    Person person = (Person) o;
    return age == person.age && name.equals(person.name);
}

// 模式匹配 equals
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Person person)) return false; // 模式匹配
    return age == person.age && name.equals(person.name);
}

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

// 处理多态对象
void process(Object obj) {
    if (obj instanceof String s) {
        System.out.println("字符串: " + s.toUpperCase());
    } else if (obj instanceof Integer i) {
        System.out.println("整数: " + (i * 2));
    } else if (obj instanceof List<?> list) {
        System.out.println("列表大小: " + list.size());
    } else if (obj instanceof Map<?, ?> map) {
        System.out.println("Map 大小: " + map.size());
    } else if (obj == null) {
        System.out.println("null");
    } else {
        System.out.println("未知类型: " + obj.getClass().getSimpleName());
    }
}

// JSON 值处理
Object jsonValue = Map.of("name", "Alice", "age", 25);

if (jsonValue instanceof Map<?, ?> map) {
    map.forEach((key, value) -> {
        System.out.println(key + ": " + value);
    });
}

// 异常处理
try {
    // 某些操作
} catch (Exception e) {
    if (e instanceof SQLException sqlEx) {
        System.out.println("SQL 错误码: " + sqlEx.getErrorCode());
    } else if (e instanceof IOException ioEx) {
        System.out.println("IO 错误: " + ioEx.getMessage());
    } else {
        throw e;
    }
}

2.3 作用域规则

// ============ 模式变量的作用域 ============

// 1. 模式变量在 instanceof 为 true 的分支中可用
if (obj instanceof String str) {
    System.out.println(str); // OK
}
// System.out.println(str); // 编译错误:str 不在作用域内

// 2. 与 && 组合时,模式变量在右侧可用
if (obj instanceof String str && str.length() > 0) {
    System.out.println(str); // OK
}

// 3. 与 || 组合时,模式变量在右侧不可用
// if (obj instanceof String str || str.isEmpty()) // 编译错误

// 4. 在条件表达式的 else 分支中不可用
if (obj instanceof String str) {
    System.out.println(str); // OK
} else {
    // System.out.println(str); // 编译错误
}

3. Switch 表达式正式标准化

3.1 概述

JEP 361 — Switch Expressions 在 JDK 12(预览)、JDK 13(第二次预览)后,终于在 JDK 14 正式标准化。这意味着可以直接在生产环境中使用,无需 --enable-preview 参数。

3.2 代码案例

// ============ 1. 基础 Switch 表达式 ============

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

// 箭头语法(无 fall-through)
int numLetters = switch (Day.MONDAY) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case WEDNESDAY, THURSDAY    -> 9;
    case SATURDAY               -> 8;
};

// ============ 2. yield 关键字 ============

// 冒号形式 + yield
String describe = switch (Day.MONDAY) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        yield "休息或工作";
    case TUESDAY:
    case WEDNESDAY:
    case THURSDAY:
        yield "工作日";
    case SATURDAY:
        yield "周末";
    default:
        yield "未知";
};

// ============ 3. 箭头形式 + 代码块 + yield ============

String result = switch ("JAVA") {
    case "JAVA" -> {
        String desc = "Java 编程语言";
        yield desc;
    }
    case "PYTHON" -> {
        yield "Python 编程语言";
    }
    default -> "未知语言";
};

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

// HTTP 状态码
String httpStatus(int code) {
    return switch (code) {
        case 200 -> "OK";
        case 201 -> "Created";
        case 204 -> "No Content";
        case 400 -> "Bad Request";
        case 401 -> "Unauthorized";
        case 403 -> "Forbidden";
        case 404 -> "Not Found";
        case 500 -> "Internal Server Error";
        default -> "Unknown";
    };
}

// 类型判断(配合 instanceof 模式匹配)
String format(Object obj) {
    return switch (obj) {
        case null -> "null";
        case String s -> "String: " + s;
        case Integer i -> "Integer: " + i;
        case Long l -> "Long: " + l;
        default -> "Object: " + obj.toString();
    };
}
// 注意:上面的 switch on Object 需要 JDK 17+ 的模式匹配
// JDK 14 中 switch 只能用于基本类型、String、enum

// 月份所属季度
int getQuarter(int month) {
    return switch (month) {
        case 1, 2, 3 -> 1;
        case 4, 5, 6 -> 2;
        case 7, 8, 9 -> 3;
        case 10, 11, 12 -> 4;
        default -> throw new IllegalArgumentException("Invalid month: " + month);
    };
}

// 颜色名称
String colorName(String hex) {
    return switch (hex.toUpperCase()) {
        case "#FF0000", "#RED" -> "红色";
        case "#00FF00", "#GREEN" -> "绿色";
        case "#0000FF", "#BLUE" -> "蓝色";
        case "#FFFFFF", "#WHITE" -> "白色";
        case "#000000", "#BLACK" -> "黑色";
        default -> "未知颜色: " + hex;
    };
}

4. Text Blocks 文本块(第二次预览)

4.1 概述

JEP 368 — Text Blocks 在 JDK 13(首次预览)后,JDK 14 进行了第二次预览,新增了三个重要能力:

  1. \ 行尾续行符 — 忽略换行,拼接下一行(JDK 13 已有)
  2. 新增 \s 转义 — 表示单个空格(JDK 13 已有)
  3. 新增转义规则 — 更灵活的空白处理

4.2 代码案例

// ============ 1. 基础文本块 ============

String json = """
    {
      "name": "Alice",
      "age": 25
    }
    """;

// ============ 2. \s 转义(保持行尾空格) ============

// 某些格式要求行尾有空格
String table = """
    Name\s\s| Age | City\s\s\s
    --------|-----|----------
    Alice\s\s| 25\s| Beijing\s
    Bob\s\s\s| 30\s| Shanghai
    """;
// \s 确保行尾空格被保留

// ============ 3. \ 行尾续行 ============

// 不使用续行符
String withNewline = """
    Hello
    World""";
// "Hello\nWorld"

// 使用续行符(忽略换行)
String withoutNewline = """
    Hello \
    World""";
// "Hello World"(中间没有换行)

// 多行续行
String longLine = """
    This is a very \
    long line that \
    spans multiple \
    source lines""";
// "This is a very long line that spans multiple source lines"

// ============ 4. 与 formatted() 配合 ============

String html = """
    <html>
      <body>
        <h1>%s</h1>
        <p>Welcome, %s! You have %d messages.</p>
      </body>
    </html>
    """.formatted("消息中心", "Alice", 5);

// ============ 5. 缩进控制技巧 ============

// 结尾 """ 在最左边 → 无去缩进
String noIndent = """
Line A
Line B
Line C
""";

// 结尾 """ 与内容对齐 → 按内容最小缩进去缩进
String aligned = """
    Line A
    Line B
    Line C
    """;
// 结果:每行前面没有多余缩进

// 混合缩进
String mixed = """
    Line A
      Line B(缩进2空格)
        Line C(缩进4空格)
    """;
// 相对基准的缩进被保留

// ============ 6. SQL 查询 ============

String sql = """
    SELECT u.id, u.name, u.email,
           COUNT(o.id) AS order_count,
           SUM(o.total) AS total_spent
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.status = ?
    GROUP BY u.id, u.name, u.email
    HAVING COUNT(o.id) > ?
    ORDER BY total_spent DESC
    """;

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

// JSON 模板
String buildJson(String name, int age) {
    return """
        {
          "name": "%s",
          "age": %d,
          "timestamp": "%s"
        }
        """.formatted(name, age, java.time.LocalDateTime.now());
}

// HTML 邮件模板
String buildEmail(String userName, String orderNo) {
    return """
        <html>
        <body>
          <h2>订单确认</h2>
          <p>尊敬的 %s,</p>
          <p>您的订单 <strong>%s</strong> 已确认。</p>
          <p>感谢您的购买!</p>
        </body>
        </html>
        """.formatted(userName, orderNo);
}

5. Helpful NullPointerExceptions 空指针异常增强

5.1 概述

JEP 358 — Helpful NullPointerExceptions(更友好的空指针异常信息)。JDK 14 改进了 NPE 的异常消息,精确指出哪个变量为 null以及哪个操作导致了 NPE,极大提升了调试效率。

改进前:NullPointerException(无有用信息)
改进后:Cannot invoke "String.length()" because "str" is null

5.2 代码案例

// ============ 1. 改进前的 NPE 消息 ============

// JDK 13 及之前
String str = null;
str.length();
// 异常消息:null(没有任何有用信息)

// ============ 2. 改进后的 NPE 消息 ============

// JDK 14+
String str = null;
str.length();
// 异常消息:Cannot invoke "String.length()" because "str" is null

// ============ 3. 链式调用场景 ============

class User {
    Address address;
}
class Address {
    String city;
}

User user = new User();
user.address = null;

// JDK 13 及之前
String city = user.address.city.toUpperCase();
// 异常消息:null
// 不知道是 user 为 null、address 为 null、还是 city 为 null

// JDK 14+
String city = user.address.city.toUpperCase();
// 异常消息:Cannot read field "city" because "user.address" is null
// 精确定位:是 address 为 null

// ============ 4. 各种场景的 NPE 消息 ============

// 4.1 方法调用
Object obj = null;
obj.toString();
// Cannot invoke "Object.toString()" because "obj" is null

// 4.2 字段访问
class Container {
    String value;
}
Container container = new Container();
container.value = null;
int len = container.value.length();
// Cannot invoke "String.length()" because "container.value" is null

// 4.3 数组访问
int[] arr = null;
int val = arr[0];
// Cannot load from object array because "arr" is null

// 4.4 数组元素方法调用
String[] strings = null;
int len = strings[0].length();
// Cannot load from object array because "strings" is null

// 4.5 嵌套字段
class Outer {
    Inner inner;
}
class Inner {
    String name;
}
Outer outer = new Outer();
outer.inner = null;
String name = outer.inner.name;
// Cannot read field "name" because "outer.inner" is null

// ============ 5. 启用方式 ============

// JDK 14 默认启用
// 如果需要关闭(不推荐):
// java -XX:-ShowCodeDetailsInExceptionMessages -jar app.jar

// ============ 6. 实际影响 ============

// 改进前:调试 NPE 需要
// 1. 看堆栈找到行号
// 2. 打开源码看那一行
// 3. 逐个检查哪些变量可能为 null
// 4. 加日志或断点排查

// 改进后:
// 1. 看异常消息就知道哪个变量为 null
// 2. 直接定位问题根因

// 生产环境示例
public class UserService {
    public String getCity(User user) {
        return user.getAddress().getCity().toUpperCase();
    }
}

// JDK 13: NullPointerException(需要逐步排查)
// JDK 14+: Cannot invoke "Address.getCity()" because the return value of
//           "User.getAddress()" is null
// 一目了然:getAddress() 返回了 null

6. Foreign Memory Access API(孵化)

6.1 概述

JEP 370 — Foreign Memory Access API(外部内存访问 API)以孵化形式引入。该 API 允许 Java 程序安全高效地访问堆外内存(native memory),用于与本地库交互、处理高性能计算场景。

核心类:

  • MemorySegment — 内存段(连续内存区域)
  • MemoryAddress — 内存地址
  • MemoryLayout — 内存布局描述
  • SegmentAllocator — 内存分配器

6.2 代码案例

// ============ 1. 分配堆外内存 ============

import jdk.incubator.foreign.*;
import static jdk.incubator.foreign.MemoryLayout.*;

// 分配 100 字节的堆外内存
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
    // 使用完毕后自动释放(AutoCloseable)
    MemoryAddress base = segment.baseAddress();
    System.out.println("内存地址: " + base);
}

// ============ 2. 使用 MemoryLayout 描述内存布局 ============

// 描述一个 C 结构体:
// struct Point {
//     int x;
//     int y;
// };
MemoryLayout pointLayout = MemoryLayout.structLayout(
    ValueLayout.JAVA_INT.withName("x"),
    ValueLayout.JAVA_INT.withName("y")
);

// 分配匹配该布局的内存
try (MemorySegment segment = MemorySegment.allocateNative(pointLayout)) {
    // 访问字段
    VarHandle xHandle = pointLayout.varHandle(int.class, MemoryLayout.PathElement.groupElement("x"));
    VarHandle yHandle = pointLayout.varHandle(int.class, MemoryLayout.PathElement.groupElement("y"));

    MemoryAddress base = segment.baseAddress();
    xHandle.set(base, 10);  // x = 10
    yHandle.set(base, 20);  // y = 20

    System.out.println("x = " + xHandle.get(base)); // 10
    System.out.println("y = " + yHandle.get(base)); // 20
}

// ============ 3. 数组操作 ============

// 分配 int 数组(10 个元素)
MemoryLayout arrayLayout = MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_INT);

try (MemorySegment segment = MemorySegment.allocateNative(arrayLayout)) {
    VarHandle elemHandle = arrayLayout.varHandle(int.class,
        MemoryLayout.PathElement.sequenceElement());

    MemoryAddress base = segment.baseAddress();

    // 写入数据
    for (int i = 0; i < 10; i++) {
        elemHandle.set(base, (long) i, i * i); // arr[i] = i*i
    }

    // 读取数据
    for (int i = 0; i < 10; i++) {
        System.out.print(elemHandle.get(base, (long) i) + " ");
    }
    // 0 1 4 9 16 25 36 49 64 81
}

// ============ 4. 与 ByteBuffer 互转 ============

// ByteBuffer → MemorySegment
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
try (MemorySegment segment = MemorySegment.ofByteBuffer(buffer)) {
    // 通过 MemorySegment 操作
    segment.asSlice(0, 4).fill((byte) 42);
}

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

// 与 C 库交互(通过 JNI/JNA 的替代方案)
// 调用 C 函数:int add(int a, int b)
// 通过 FunctionDescriptor 和 MethodHandle 调用

// 高性能计算
// 直接操作堆外内存,避免 Java 堆内存 GC 开销

// 网络编程
// 零拷贝(zero-copy)场景,直接操作 DMA 缓冲区

// ============ 6. 编译与运行 ============

// 孵化 API 需要额外参数
// javac --add-modules jdk.incubator.foreign App.java
// java --add-modules jdk.incubator.foreign App

7. JFR 事件流

7.1 概述

JEP 349 — JFR(Java Flight Recorder)事件流。JDK 14 允许以流式方式实时消费 JFR 事件,而无需先导出为文件再分析。这使得实时监控和诊断成为可能。

7.2 代码案例

// ============ 1. 基础事件流 ============

import jdk.jfr.consumer.*;
import java.nio.file.Path;
import java.time.Duration;

// 创建事件流(实时)
try (RecordingStream stream = new RecordingStream()) {
    // 设置事件过滤
    stream.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
    stream.enable("jdk.GCHeapSummary").withPeriod(Duration.ofSeconds(5));

    // 注册事件处理器
    stream.onEvent("jdk.CPULoad", event -> {
        double jvmCpuLoad = event.getFloat("jvmUser") + event.getFloat("jvmSystem");
        System.out.printf("JVM CPU 使用率: %.1f%%%n", jvmCpuLoad * 100);
    });

    stream.onEvent("jdk.GCHeapSummary", event -> {
        long heapUsed = event.getLong("heapUsed");
        long heapTotal = event.getLong("heapTotal");
        System.out.printf("堆内存: %d MB / %d MB%n",
            heapUsed / 1024 / 1024, heapTotal / 1024 / 1024);
    });

    // 设置超时(运行 30 秒后关闭)
    stream.onEvent("jdk.CPULoad", event -> {}); // 触发流启动
    stream.start();
}

// ============ 2. 从 JFR 文件读取事件 ============

try (RecordingStream stream = new RecordingStream(Path.of("recording.jfr"))) {
    stream.onEvent("jdk.ObjectAllocationInNewTLAB", event -> {
        String className = event.getClass("objectClass").getName();
        long allocationSize = event.getLong("allocationSize");
        System.out.printf("分配: %s (%d bytes)%n", className, allocationSize);
    });

    stream.start();
}

// ============ 3. 自定义事件 + 流消费 ============

import jdk.jfr.*;

@Name("com.example.HttpRequest")
@Label("HTTP Request")
@Description("Tracks HTTP request processing")
public class HttpRequestEvent extends Event {
    @Label("URL")
    String url;

    @Label("Method")
    String method;

    @Label("Status Code")
    int statusCode;

    @Label("Duration (ms)")
    long durationMs;
}

// 发布事件
HttpRequestEvent event = new HttpRequestEvent();
event.url = "/api/users";
event.method = "GET";
event.statusCode = 200;
event.durationMs = 45;
event.commit();

// 流式消费自定义事件
try (RecordingStream stream = new RecordingStream()) {
    stream.enable("com.example.HttpRequest");

    stream.onEvent("com.example.HttpRequest", e -> {
        System.out.printf("[%s] %s %s → %d (%dms)%n",
            e.getStartTime(),
            e.getString("method"),
            e.getString("url"),
            e.getInt("statusCode"),
            e.getLong("durationMs"));
    });

    stream.start();
}

// ============ 4. 实时监控面板 ============

// 模拟一个简单的实时监控
try (RecordingStream stream = new RecordingStream()) {
    stream.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
    stream.enable("jdk.ThreadStatistics").withPeriod(Duration.ofSeconds(2));

    stream.onEvent("jdk.CPULoad", event -> {
        double machineTotal = event.getFloat("machineTotal");
        double jvmTotal = event.getFloat("jvmUser") + event.getFloat("jvmSystem");
        System.out.printf("[CPU] 系统: %.1f%% | JVM: %.1f%%%n",
            machineTotal * 100, jvmTotal * 100);
    });

    stream.onEvent("jdk.ThreadStatistics", event -> {
        long threadCount = event.getLong("activeThreadCount");
        long daemonCount = event.getLong("daemonThreadCount");
        System.out.printf("[线程] 活跃: %d | 守护: %d%n", threadCount, daemonCount);
    });

    stream.start();
}

// ============ 5. 编译与运行 ============

// JFR 事件流不需要 --enable-preview
// javac App.java
// java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder App

8. Packaging Tool 打包工具(孵化)

8.1 概述

JEP 343 — Packaging Tool(打包工具)以孵化形式引入,用于创建平台原生安装包(Linux 的 deb/rpm、macOS 的 pkg/dmg、Windows 的 msi/exe)。

8.2 使用方式

# ============ 1. 基础打包命令 ============

# 创建 Linux deb 包
jpackage --name MyApp \
         --input target/libs \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type deb \
         --dest output/

# 创建 Windows msi 包
jpackage --name MyApp \
         --input target/libs \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type msi \
         --dest output/

# 创建 macOS pkg 包
jpackage --name MyApp \
         --input target/libs \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type pkg \
         --dest output/

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

jpackage --name "My Application" \
         --app-version 1.0.0 \
         --vendor "My Company" \
         --description "My awesome application" \
         --input target/libs \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --java-options "-Xmx512m" \
         --java-options "-Dfile.encoding=UTF-8" \
         --icon resources/icon.ico \
         --dest output/

# ============ 3. 创建带运行时的安装包 ============

# 包含 JRE(用户无需安装 Java)
jpackage --name MyApp \
         --runtime-image target/runtime-image \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type exe \
         --dest output/

# ============ 4. 配置文件方式 ============

# 创建配置文件 myapp.properties
# type=deb
# name=MyApp
# app-version=1.0.0
# vendor=MyCompany
# input=target/libs
# main-jar=myapp.jar
# main-class=com.example.Main
# java-options=-Xmx512m
# dest=output/

# 使用配置文件
jpackage @myapp.properties

9. ZGC 跨平台支持

9.1 概述

JDK 14 将 ZGC(Z Garbage Collector)扩展到了 macOS(JEP 364)和 Windows(JEP 365)平台。此前 ZGC 仅支持 Linux。

9.2 使用方式

# ============ 1. 启用 ZGC ============

# macOS
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar app.jar

# Windows
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar app.jar

# ============ 2. 配合容器使用 ============

# Docker(Linux 环境,ZGC 完整支持)
docker run -m 4g java \
    -XX:+UnlockExperimentalVMOptions \
    -XX:+UseZGC \
    -XX:MaxRAMPercentage=75.0 \
    -jar app.jar

# ============ 3. ZGC 在各平台的状态 ============

# Linux:   实验性(JDK 11)→ 生产可用(JDK 15)
# macOS:   实验性(JDK 14)
# Windows: 实验性(JDK 14)

10. CMS GC 移除

10.1 概述

JEP 363 — 移除 Concurrent Mark Sweep(CMS)垃圾收集器。CMS 在 JDK 9 已被标记为废弃(deprecated),JDK 14 正式将其移除。

10.2 影响

# ============ 1. 移除前 ============

# JDK 13 及之前可以使用 CMS
java -XX:+UseConcMarkSweepGC -jar app.jar

# ============ 2. 移除后 ============

# JDK 14+ 使用 CMS 会报错
java -XX:+UseConcMarkSweepGC -jar app.jar
# 错误:Unrecognized VM option 'UseConcMarkSweepGC'

# ============ 3. 迁移建议 ============

# CMS → G1(推荐,JDK 9+ 默认)
java -XX:+UseG1GC -jar app.jar

# CMS → ZGC(低延迟场景)
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar app.jar

# CMS → Shenandoah(低延迟,OpenJDK 提供)
java -XX:+UseShenandoahGC -jar app.jar

# ============ 4. 各 GC 对比 ============

# | GC          | 特点              | 适用场景        |
# |-------------|-------------------|-----------------|
# | Serial GC   | 单线程,最简单     | 小堆、容器      |
# | Parallel GC | 多线程,高吞吐     | 批处理          |
# | G1 GC       | 分区,可预测暂停   | 通用(默认)    |
# | ZGC        | 超低延迟(<10ms)  | 大堆、低延迟    |
# | Shenandoah | 低延迟,并发压缩   | 低延迟          |

11. 其他重要变更

11.1 废弃的 API 和特性

// ============ 1. 移除的特性 ============

// 1. CMS GC 移除(JEP 363)
// 2. ParallelScavenge + Serial GC 组合的废弃通知
// 3. 移除了部分旧的 AWT/Swing 相关 API

// ============ 2. 废弃的 API ============

// Biased Locking(偏向锁)开始标记为废弃
// -XX:+UseBiasedLocking 在未来版本可能移除

// ============ 3. 新的启动方式 ============

// source-file 单文件运行增强
// 可以直接运行包含 shebang 的 Java 文件

// #!/usr/bin/java --source 14
// public class HelloWorld {
//     public static void main(String[] args) {
//         System.out.println("Hello from Java 14!");
//     }
// }

// 在 Linux/macOS 上可以直接执行
// chmod +x HelloWorld.java
// ./HelloWorld.java

11.2 macOS/AArch64 移植准备

JDK 14 为 Apple Silicon(M1/M2)芯片做了前期准备工作,虽然正式的 macOS/AArch64 移植在 JDK 17 完成,但 JDK 14 开始了相关基础设施搭建。


12. JDK 14 特性总览表

序号特性JEP类型重要性
1Records 记录类(预览)JEP 395语言★★★★★
2Pattern Matching for instanceof(预览)JEP 305语言★★★★★
3Switch 表达式正式标准化JEP 361语言★★★★★
4Text Blocks 文本块(第二次预览)JEP 368语言★★★★☆
5Helpful NullPointerExceptionsJEP 358调试★★★★★
6Foreign Memory Access API(孵化)JEP 370API★★★★☆
7JFR 事件流JEP 349监控★★★★☆
8Packaging Tool 打包工具(孵化)JEP 343工具★★★☆☆
9ZGC on macOSJEP 364GC★★★☆☆
10ZGC on WindowsJEP 365GC★★★☆☆
11CMS GC 移除JEP 363GC★★★★☆

附录:预览特性状态追踪

特性JDK 12JDK 13JDK 14JDK 15JDK 16JDK 17
Switch 表达式预览第二次预览正式———
Text Blocks—预览第二次预览正式——
Records——预览第二次预览正式—
Pattern Matching instanceof——预览第二次预览正式—
Foreign Memory Access——孵化孵化孵化孵化
Packaging Tool——孵化孵化正式—

总结:JDK 14 是一个特性密集的版本。Records 为数据类提供了极简语法,Pattern Matching for instanceof 减少了类型转换的样板代码,Switch 表达式正式标准化 标志着这一重要语言特性的成熟,Helpful NullPointerExceptions 极大提升了调试体验,JFR 事件流 让实时监控成为可能。JDK 14 为后续 LTS 版本(JDK 17)的诸多正式特性奠定了基础。

Logo

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

更多推荐