引言:为什么升级 JDK 17 是必然选择
自 2014 年 JDK 8 发布以来,Java 已经走过了九个年头。这九年间,Java 经历了从 Oracle 独家管理到 Oracle、IBM、Red Hat 等多家厂商共同治理的转变,发布周期也从原来的 "遥遥无期" 变为严格的六个月一次。JDK 17 作为 2021 年 9 月发布的长期支持版本(LTS),汇集了从 JDK 9 到 JDK 17 的所有重要特性,是继 JDK 8 之后最值得升级的版本。
据 JetBrains 2023 年开发者调查显示,JDK 17 的采用率已经超过 JDK 8,成为生产环境中使用最广泛的 Java 版本。这一趋势背后,是 JDK 17 带来的显著性能提升、更简洁的语法、更强的安全性和更丰富的 API。
本文将全面剖析 JDK 17 相比 JDK 8 的重大改进,从语言特性到性能优化,从 API 增强到安全提升,让你全方位了解这九年里 Java 发生的翻天覆地的变化,为你的升级决策提供权威参考。

一、语言特性的革命性演进
JDK 17 在语言特性上相比 JDK 8 有了质的飞跃,引入了许多简化代码、提高可读性的新特性。这些特性大多是在 JDK 9 到 JDK 17 之间逐步引入的,共同构成了现代 Java 的语法基础。
1.1 局部变量类型推断:var 关键字(JDK 10)
JDK 10 引入的 var 关键字允许编译器根据变量的初始值推断其类型,减少了代码的冗余,同时保持了静态类型的安全性。
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
@Slf4j
public class VarExample {
public static void main(String[] args) {
// JDK 8 写法
String message = "Hello, JDK 8";
ArrayList<String> list = new ArrayList<>();
HashMap<String, Integer> map = new HashMap<>();
// JDK 17 写法
var jdk17Message = "Hello, JDK 17";
var jdk17List = new ArrayList<String>();
var jdk17Map = new HashMap<String, Integer>();
log.info("JDK 8 message: {}", message);
log.info("JDK 17 message: {}", jdk17Message);
// var 仍然是强类型,编译时会确定具体类型
jdk17List.add("元素1");
jdk17Map.put("key1", 1);
log.info("列表大小: {}", jdk17List.size());
log.info("映射值: {}", jdk17Map.get("key1"));
}
}
使用注意事项:
- var 只能用于局部变量,不能用于字段、方法参数或返回类型
- 变量必须初始化,编译器才能推断类型
- 不能用于 lambda 表达式或方法引用的类型推断
1.2 文本块:简化多行字符串(JDK 15)
JDK 15 引入的文本块功能解决了 JDK 8 中处理多行字符串的痛点,使用三个双引号 (""") 包裹的文本可以保留换行和缩进,极大简化了 SQL、JSON、HTML 等多行字符串的编写。
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
@Slf4j
public class TextBlockExample {
public static void main(String[] args) {
// JDK 8 处理多行字符串
String jdk8Json = "{\n" +
" \"name\": \"Java\",\n" +
" \"version\": 8,\n" +
" \"features\": [\"Lambda\", \"Stream\", \"Optional\"]\n" +
"}";
// JDK 17 文本块
var jdk17Json = """
{
"name": "Java",
"version": 17,
"features": ["var", "Text Block", "Sealed Classes"]
}
""";
log.info("JDK 8 JSON:\n{}", jdk8Json);
log.info("JDK 17 JSON:\n{}", jdk17Json);
// 文本块可以使用格式化
var userInfo = """
User Information:
Name: %s
Age: %d
Email: %s
""".formatted("张三", 30, "zhangsan@example.com");
log.info("格式化用户信息:\n{}", userInfo);
}
}
文本块的优势:
- 无需转义换行符和引号
- 保留自然的格式和缩进
- 支持
formatted()方法进行格式化 - 可以通过
stripIndent()等方法处理缩进
1.3 密封类:控制类的继承关系(JDK 17)
密封类(Sealed Classes)是 JDK 17 正式引入的特性,它允许类的作者指定哪些类可以继承它,从而增强了代码的封装性和可维护性。
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SealedClassExample {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
log.info("圆的面积: {}", circle.calculateArea());
log.info("矩形的面积: {}", rectangle.calculateArea());
}
/**
* 密封类Shape,只允许指定的类继承它
*/
public sealed abstract class Shape permits Circle, Rectangle {
/**
* 计算面积
* @return 面积
*/
public abstract double calculateArea();
}
/**
* 圆,继承自Shape
*/
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
/**
* 矩形,继承自Shape
*/
public final class Rectangle extends Shape {
private final double length;
private final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
}
密封类的特点:
- 使用
sealed关键字声明,permits指定允许继承的类 - 被允许的子类必须使用
final(不能再被继承)、sealed(继续限制继承)或non-sealed(取消限制)修饰 - 增强了代码的可预测性,避免了意外的继承和重写
1.4 记录类:简化数据载体类(JDK 16)
记录类(Records)是 JDK 16 引入的一种新的类声明形式,专门用于创建不可变的数据载体类。它自动生成了构造方法、equals ()、hashCode () 和 toString () 等方法,大幅减少了模板代码。
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class RecordExample {
/**
* 记录类:用户信息
* 自动生成所有字段的构造方法、getter、equals、hashCode和toString
*/
public record User(Long id, String name, int age, String email) {}
/**
* 记录类:订单信息
*/
public record Order(Long orderId, Long userId, double amount, List<String> products) {}
public static void main(String[] args) {
// 创建用户对象
var user = new User(1L, "张三", 30, "zhangsan@example.com");
log.info("用户信息: {}", user);
log.info("用户名: {}", user.name()); // 注意:记录类使用name()而非getName()
// 创建订单对象
var products = new ArrayList<String>();
products.add("Java编程思想");
products.add("Effective Java");
var order = new Order(1001L, user.id(), 199.99, products);
log.info("订单信息: {}", order);
// 记录类是不可变的,无法修改字段值
// user.name("李四"); // 编译错误:记录类没有setter方法
// 测试equals和hashCode
var user2 = new User(1L, "张三", 30, "zhangsan@example.com");
log.info("user == user2? {}", user.equals(user2)); // true
}
}
记录类 vs 传统类:

1.5 增强的 switch 表达式(JDK 14)
JDK 14 对 switch 语句进行了重大增强,使其可以作为表达式使用,支持箭头语法和 yield 返回值,大大提高了代码的简洁性和可读性。
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SwitchExpressionExample {
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
public static void main(String[] args) {
// JDK 8 switch语句
Season season = Season.SPRING;
String message;
switch (season) {
case SPRING:
message = "春天适合踏青";
break;
case SUMMER:
message = "夏天适合游泳";
break;
case AUTUMN:
message = "秋天适合赏秋";
break;
case WINTER:
message = "冬天适合滑雪";
break;
default:
message = "未知季节";
}
log.info("JDK 8 switch结果: {}", message);
// JDK 17 switch表达式
var jdk17Message = switch (season) {
case SPRING -> "春天适合踏青";
case SUMMER -> "夏天适合游泳";
case AUTUMN -> "秋天适合赏秋";
case WINTER -> "冬天适合滑雪";
};
log.info("JDK 17 switch表达式结果: {}", jdk17Message);
// 复杂情况使用yield返回值
var temperatureAdvice = switch (season) {
case SPRING -> {
log.info("春天温度适中");
yield "穿薄外套即可";
}
case SUMMER -> {
log.info("夏天比较炎热");
yield "注意防晒降温";
}
case AUTUMN -> {
log.info("秋天温差较大");
yield "注意增减衣物";
}
case WINTER -> {
log.info("冬天非常寒冷");
yield "注意保暖防冻";
}
};
log.info("季节温度建议: {}", temperatureAdvice);
}
}
增强 switch 的优势:
- 可以作为表达式返回值
- 使用箭头语法 (
->) 替代:, 无需 break 防止穿透 - 支持 yield 关键字在代码块中返回值
- 编译时检查是否覆盖所有可能的情况
1.6 instanceof 模式匹配(JDK 16)
JDK 16 引入的 instanceof 模式匹配允许在类型检查的同时进行变量赋值,消除了冗余的强制类型转换,使代码更简洁。
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class InstanceOfExample {
public static void main(String[] args) {
Object obj = new ArrayList<String>();
obj = new HashMap<String, Integer>();
// JDK 8 写法
if (obj instanceof List) {
List<?> list = (List<?>) obj;
log.info("JDK 8: 这是一个列表,大小为: {}", list.size());
} else if (obj instanceof Map) {
Map<?, ?> map = (Map<?, ?>) obj;
log.info("JDK 8: 这是一个映射,大小为: {}", map.size());
}
// JDK 17 模式匹配写法
if (obj instanceof List<?> list) {
log.info("JDK 17: 这是一个列表,大小为: {}", list.size());
} else if (obj instanceof Map<?, ?> map) {
log.info("JDK 17: 这是一个映射,大小为: {}", map.size());
}
// 实际应用示例
processData("测试字符串");
processData(12345);
processData(new User(1L, "张三"));
}
/**
* 处理不同类型的数据
* @param data 待处理的数据
*/
private static void processData(Object data) {
if (data instanceof String str) {
log.info("处理字符串: 长度为 {}, 内容为 {}", str.length(), str);
} else if (data instanceof Integer num) {
log.info("处理整数: 平方为 {}", num * num);
} else if (data instanceof User user) {
log.info("处理用户对象: ID={}, 姓名={}", user.id(), user.name());
} else {
log.info("处理未知类型: {}", data.getClass().getSimpleName());
}
}
// 记录类:用户
public record User(Long id, String name) {}
}
模式匹配的优势:
- 减少了一次显式的类型转换
- 变量的作用域被限制在条件块内,提高了代码安全性
- 使代码更简洁,逻辑更清晰
二、API 的重大增强
JDK 17 在 API 层面引入了许多实用的新功能和改进,涵盖了集合、IO、并发、日期时间等多个领域,极大地提升了开发效率。
2.1 集合框架的增强
JDK 9 及以后版本对集合框架进行了多项增强,主要包括便捷的创建方法和新的集合操作。
2.1.1 集合工厂方法(JDK 9)
JDK 9 为 List、Set 和 Map 添加了静态工厂方法of(),用于创建不可变集合,简化了集合的创建过程。
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Slf4j
public class CollectionFactoryExample {
public static void main(String[] args) {
// JDK 8 创建不可变集合
List<String> jdk8List = java.util.Collections.unmodifiableList(
java.util.Arrays.asList("a", "b", "c")
);
// JDK 17 创建不可变集合
List<String> jdk17List = List.of("a", "b", "c");
Set<String> set = Set.of("x", "y", "z");
Map<String, Integer> map = Map.of(
"one", 1,
"two", 2,
"three", 3
);
log.info("列表元素: {}", jdk17List);
log.info("集合元素: {}", set);
log.info("映射元素: {}", map);
// 尝试修改会抛出UnsupportedOperationException
try {
jdk17List.add("d");
} catch (UnsupportedOperationException e) {
log.info("尝试修改不可变列表: {}", e.getMessage());
}
// 创建包含10个以上元素的映射
Map<String, Integer> largeMap = Map.ofEntries(
Map.entry("one", 1),
Map.entry("two", 2),
Map.entry("three", 3),
Map.entry("four", 4),
Map.entry("five", 5),
Map.entry("six", 6),
Map.entry("seven", 7),
Map.entry("eight", 8),
Map.entry("nine", 9),
Map.entry("ten", 10),
Map.entry("eleven", 11)
);
log.info("包含11个元素的映射: {}", largeMap);
}
}
工厂方法创建的集合特点:
- 不可变:不能添加、删除或修改元素
- 不允许 null 元素(与 HashMap 等不同)
- 实现了更好的内存优化
- 适合创建常量集合或配置数据
2.1.2 集合的 toList () 方法(JDK 16)
JDK 16 为 Stream 添加了toList()方法,提供了一个更简洁的方式将流转换为不可变列表。
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
public class StreamToListExample {
public static void main(String[] args) {
List<String> languages = Arrays.asList("Java", "Python", "C++", "JavaScript", "Ruby");
// JDK 8 转换为列表
List<String> jdk8Result = languages.stream()
.filter(lang -> lang.startsWith("J"))
.collect(Collectors.toList());
// JDK 17 转换为不可变列表
List<String> jdk17ImmutableResult = languages.stream()
.filter(lang -> lang.startsWith("J"))
.toList();
// 转换为可变列表仍然可以使用Collectors.toList()
List<String> jdk17MutableResult = languages.stream()
.filter(lang -> lang.startsWith("J"))
.collect(Collectors.toList());
log.info("JDK 8 结果: {}", jdk8Result);
log.info("JDK 17 不可变结果: {}", jdk17ImmutableResult);
// 测试可变性
jdk17MutableResult.add("Julia");
log.info("添加元素后的可变列表: {}", jdk17MutableResult);
try {
jdk17ImmutableResult.add("Julia");
} catch (UnsupportedOperationException e) {
log.info("尝试修改不可变列表: {}", e.getMessage());
}
}
}
toList () 与 Collectors.toList () 的区别:
toList()返回不可变列表,Collectors.toList()返回可变列表toList()的实现更高效,内存占用更小toList()不允许添加 null 元素
2.2 日期时间 API 的增强
JDK 8 引入的日期时间 API(java.time 包)在后续版本中得到了进一步增强,添加了更多实用功能。
import lombok.extern.slf4j.Slf4j;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
@Slf4j
public class DateTimeEnhancements {
public static void main(String[] args) {
// 1. 日期周期格式化(JDK 16)
var period = Period.of(2, 3, 15);
var formatter = DateTimeFormatter.ofPattern("Y年M月d日");
log.info("格式化的周期: {}", period.format(formatter));
// 2. 更方便的LocalDate创建(JDK 16)
var date1 = LocalDate.of(2023, Month.JULY, 15);
var date2 = LocalDate.parse("2023-07-15");
// JDK 16引入的ofYearDay简化写法
var date3 = LocalDate.ofYearDay(2023, 196); // 一年中的第196天
log.info("date1: {}", date1);
log.info("date2: {}", date2);
log.info("date3: {}", date3);
log.info("date1 == date2? {}", date1.equals(date2));
log.info("date1 == date3? {}", date1.equals(date3));
// 3. 时区转换增强
var zonedDateTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
var beijingTime = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
log.info("纽约时间: {}", zonedDateTime);
log.info("北京时间: {}", beijingTime);
// 4. 计算两个日期之间的工作日(自定义方法)
var startDate = LocalDate.of(2023, 7, 1);
var endDate = LocalDate.of(2023, 7, 31);
long workdays = countWorkdays(startDate, endDate);
log.info("7月份工作日数量: {}", workdays);
}
/**
* 计算两个日期之间的工作日数量(不包括周六和周日)
* @param start 开始日期
* @param end 结束日期
* @return 工作日数量
*/
private static long countWorkdays(LocalDate start, LocalDate end) {
return ChronoUnit.DAYS.between(start, end) + 1
- start.datesUntil(end.plusDays(1))
.filter(date -> date.getDayOfWeek() == DayOfWeek.SATURDAY
|| date.getDayOfWeek() == DayOfWeek.SUNDAY)
.count();
}
}
2.3 字符串和输入输出的增强
JDK 17 对 String 类和 IO 操作也进行了多项实用增强。
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
@Slf4j
public class StringAndIOEnhancements {
public static void main(String[] args) throws IOException {
// 1. String增强方法
String str = " Java 17 新特性 ";
// isBlank() 检查字符串是否为空或仅包含空白字符(JDK 11)
log.info("'{}' is blank? {}", str, str.isBlank());
log.info("'' is blank? {}", "".isBlank());
// strip() 去除前后空白(比trim()更强大,支持Unicode空白字符)(JDK 11)
log.info("原始字符串长度: {}", str.length());
log.info("strip()处理后: '{}', 长度: {}", str.strip(), str.strip().length());
log.info("trim()处理后: '{}', 长度: {}", str.trim(), str.trim().length());
// lines() 按行分割为Stream(JDK 11)
String multiLine = "第一行\n第二行\r第三行\r\n第四行";
log.info("多行字符串: \n{}", multiLine);
log.info("行数量: {}", multiLine.lines().count());
log.info("所有行:");
multiLine.lines().forEach(line -> log.info("- {}", line));
// repeat(n) 重复字符串n次(JDK 11)
String separator = "-".repeat(20);
log.info("分隔符: {}", separator);
// 2. IO增强
Path tempFile = Files.createTempFile("demo", ".txt");
log.info("临时文件路径: {}", tempFile);
// 写入文件(JDK 11新增的writeString和readString)
String content = """
这是一个测试文件
使用JDK 17的新IO方法
写入和读取字符串更加方便
""";
Files.writeString(tempFile, content, StandardOpenOption.WRITE);
// 读取文件
String fileContent = Files.readString(tempFile);
log.info("文件内容:\n{}", fileContent);
// 复制文件(JDK 12新增的copy方法)
Path destFile = Path.of("copy_of_demo.txt");
Files.copy(tempFile, destFile, StandardOpenOption.CREATE, StandardOpenOption.REPLACE_EXISTING);
log.info("复制文件内容:\n{}", Files.readString(destFile));
// 清理临时文件
Files.deleteIfExists(tempFile);
Files.deleteIfExists(destFile);
}
}
2.4 CompletableFuture 的增强
CompletableFuture 在 JDK 9 到 JDK 12 之间得到了多项增强,使其功能更加完善。
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Slf4j
public class CompletableFutureEnhancements {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 超时控制(JDK 9)
CompletableFuture<String> futureWithTimeout = CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
log.error("任务被中断", e);
Thread.currentThread().interrupt();
}
return "任务完成";
})
// 超时控制:如果2秒内未完成则触发超时
.orTimeout(2, TimeUnit.SECONDS)
// 超时或异常时的处理
.exceptionally(ex -> {
log.error("任务执行异常", ex);
return "任务超时,返回默认结果";
});
log.info("带超时的任务结果: {}", futureWithTimeout.get());
// 2. 完成时的默认值(JDK 9)
CompletableFuture<String> futureWithDefault = CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
log.error("任务被中断", e);
Thread.currentThread().interrupt();
}
return "任务完成";
})
// 如果2秒内未完成则返回默认值,但原任务会继续执行
.completeOnTimeout("任务仍在执行,返回默认值", 2, TimeUnit.SECONDS);
log.info("带默认值的任务结果: {}", futureWithDefault.get());
// 3. 延迟执行(JDK 9)
log.info("开始等待延迟任务...");
CompletableFuture<Void> delayedFuture = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS)
.submit(() -> log.info("延迟1秒后执行的任务"))
.thenRun(() -> log.info("延迟任务的后续操作"));
delayedFuture.get();
// 4. 结果转换(JDK 12)
CompletableFuture<Integer> originalFuture = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<String> transformedFuture = originalFuture
.thenApplyToEither(
CompletableFuture.supplyAsync(() -> 20),
num -> "较大的数是: " + Math.max(num, 20)
);
log.info("转换后的结果: {}", transformedFuture.get());
}
}
三、性能的巨大飞跃
JDK 17 相比 JDK 8 在性能上有了显著提升,这主要得益于多项底层优化和新的垃圾回收器。
3.1 垃圾回收器的革新
JDK 17 引入了 ZGC 和 Shenandoah 等新一代垃圾回收器,相比 JDK 8 默认的 Parallel GC 和 CMS,它们在低延迟和高吞吐量方面有了质的飞跃。

ZGC 的主要优势:
- 暂停时间不超过 10ms,且不受堆大小影响
- 支持 TB 级别的堆大小
- 并发执行所有繁重工作
- 与 G1 相比,对 Java 应用吞吐量的影响更小
启用 ZGC 的方式:
java -XX:+UseZGC -jar application.jar
Shenandoah 的主要优势:
- 暂停时间通常在 10-50ms 之间
- 所有回收阶段都与应用线程并发执行
- 堆大小支持从几百 MB 到数 TB
- 对大堆应用特别有效
启用 Shenandoah 的方式:
java -XX:+UseShenandoahGC -jar application.jar
3.2 应用启动时间的优化
JDK 17 在应用启动时间上有显著改进,这主要得益于:
-
AppCDS(应用类数据共享)的增强:允许将应用类和依赖库的类数据预编译并缓存,减少启动时的类加载和验证时间。
-
提前编译(AOT):JDK 9 引入的 jaotc 工具可以将 Java 类编译为原生代码,进一步加快启动时间和减少初始执行的延迟。
使用 AppCDS 的示例:
# 1. 生成类列表
java -XX:DumpLoadedClassList=classes.lst -jar application.jar
# 2. 创建共享存档
java -XX:SharedClassListFile=classes.lst -XX:SharedArchiveFile=app-cds.jsa -jar application.jar
# 3. 使用共享存档运行应用
java -XX:SharedArchiveFile=app-cds.jsa -jar application.jar
3.3 JIT 编译器的改进
JDK 17 中的 JIT 编译器(C2)进行了多项优化,特别是在循环优化、自动向量化和逃逸分析方面。
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class JITOptimizationExample {
private static final int ARRAY_SIZE = 10_000_000;
public static void main(String[] args) {
// 预热JIT编译器
int[] array = new int[ARRAY_SIZE];
for (int i = 0; i < 10; i++) {
initializeArray(array);
calculateSum(array);
}
// 测试数组初始化性能
long start = System.nanoTime();
initializeArray(array);
long initTime = System.nanoTime() - start;
// 测试数组求和性能
start = System.nanoTime();
long sum = calculateSum(array);
long sumTime = System.nanoTime() - start;
log.info("数组初始化时间: {} ms", initTime / 1_000_000);
log.info("数组求和时间: {} ms", sumTime / 1_000_000);
log.info("数组总和: {}", sum);
}
/**
* 初始化数组
*/
private static void initializeArray(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
}
/**
* 计算数组总和
*/
private static long calculateSum(int[] array) {
long sum = 0;
for (int value : array) {
sum += value;
}
return sum;
}
}
在 JDK 17 中,这段代码的执行速度比 JDK 8 快约 20-30%,这主要得益于:
- 循环自动向量化:利用 CPU 的 SIMD 指令并行处理数组元素
- 更好的逃逸分析:栈上分配小型对象,减少 GC 压力
- 更高效的循环展开和优化
四、安全性的全面提升
随着网络安全日益重要,JDK 17 在安全性方面相比 JDK 8 有了全面增强,包括更强的加密算法支持、更严格的默认设置和新的安全特性。
4.1 更强的加密支持
JDK 17 支持更多现代加密算法和协议,默认禁用了一些不安全的算法。
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
@Slf4j
public class CryptographyEnhancements {
public static void main(String[] args) throws Exception {
String originalMessage = "这是一个需要加密的敏感信息";
log.info("原始消息: {}", originalMessage);
// 生成AES-256密钥(JDK 8默认不支持,需要安装JCE Unlimited Strength Jurisdiction Policy Files)
// JDK 17默认支持AES-256,无需额外配置
SecretKey secretKey = generateAesKey(256);
log.info("AES-256密钥: {}", Base64.getEncoder().encodeToString(secretKey.getEncoded()));
// 加密
String encryptedMessage = encrypt(originalMessage, secretKey);
log.info("加密后: {}", encryptedMessage);
// 解密
String decryptedMessage = decrypt(encryptedMessage, secretKey);
log.info("解密后: {}", decryptedMessage);
// 列出支持的加密算法
log.info("支持的AES模式和填充方式:");
for (String transformation : new String[]{"AES/GCM/NoPadding", "AES/CBC/PKCS5Padding", "AES/ECB/PKCS5Padding"}) {
try {
Cipher.getInstance(transformation);
log.info("- 支持: {}", transformation);
} catch (Exception e) {
log.info("- 不支持: {}", transformation);
}
}
}
/**
* 生成AES密钥
* @param keySize 密钥长度(128, 192, 256)
* @return 生成的密钥
*/
private static SecretKey generateAesKey(int keySize) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(keySize);
return keyGenerator.generateKey();
}
/**
* 加密消息
* @param message 原始消息
* @param key 加密密钥
* @return 加密后的消息(Base64编码)
*/
private static String encrypt(String message, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
// 合并IV和加密数据
byte[] combined = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
}
/**
* 解密消息
* @param encryptedMessage 加密后的消息(Base64编码)
* @param key 解密密钥
* @return 解密后的原始消息
*/
private static String decrypt(String encryptedMessage, SecretKey key) throws Exception {
byte[] combined = Base64.getDecoder().decode(encryptedMessage);
// 分离IV和加密数据(GCM模式中IV长度为12字节)
byte[] iv = new byte[12];
byte[] encrypted = new byte[combined.length - iv.length];
System.arraycopy(combined, 0, iv, 0, iv.length);
System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new javax.crypto.spec.GCMParameterSpec(128, iv));
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
}
}
JDK 17 在加密方面的改进:
- 默认支持 AES-256,无需额外安装 JCE 策略文件
- 新增对 ChaCha20-Poly1305 等现代加密算法的支持
- TLS 协议支持升级到 TLS 1.3
- 增强了证书验证和密钥管理
4.2 更严格的默认安全设置
JDK 17 收紧了多项安全默认设置,减少了潜在的安全风险:
- 默认禁用不安全的 SSL/TLS 协议:如 SSLv3、TLSv1 和 TLSv1.1
- 增强的证书验证:更严格的证书链验证规则
- 限制序列化:默认禁用某些危险的序列化操作
- 增强的访问控制:更严格的模块访问控制
4.3 密封的 JAR 文件
JDK 9 引入的密封 JAR 文件功能允许开发者指定 JAR 中的哪些包是密封的,防止在其他 JAR 中定义相同包的类,从而增强了安全性。
创建密封 JAR 的 MANIFEST.MF 示例:
Manifest-Version: 1.0
Sealed: true
Name: com/example/util/
Sealed: false
这个清单文件指定整个 JAR 是密封的,但com/example/util/包除外。
五、模块化系统:Jigsaw 项目(JDK 9)
JDK 9 引入的模块化系统(Project Jigsaw)是 Java 平台自 JDK 5 以来最重大的架构变革,它解决了长期存在的 "JAR 地狱" 问题,提供了更好的封装性和安全性。
5.1 模块的基本概念
模块是一个命名的、自包含的代码和数据集合,它声明了:
- 它提供的功能(导出的包)
- 它依赖的其他模块( requires )
- 它提供的服务( provides )
- 它使用的服务( uses )

5.2 模块声明示例
// module-info.java
/**
* 用户服务模块
* 提供用户管理相关功能
*/
module com.example.user {
// 依赖的模块
requires java.base; // 隐式依赖,可省略
requires java.logging;
requires com.example.common;
requires transitive com.example.api; // 传递依赖
// 导出的包(其他模块可见)
exports com.example.user.service;
exports com.example.user.model to com.example.web; // 仅对指定模块可见
// 提供服务实现
provides com.example.api.UserService with com.example.user.service.impl.UserServiceImpl;
// 使用的服务
uses com.example.api.LoggingService;
}
5.3 模块化应用的优势
- 更好的封装性:只有明确导出的包才能被其他模块访问
- 减少内存占用:可以只加载应用需要的模块
- 更强的安全性:模块间的访问受到严格控制
- 更清晰的依赖关系:显式声明的依赖关系使系统结构更清晰
- 可定制的运行时镜像:可以使用 jlink 工具创建只包含必要模块的自定义运行时镜像
5.4 创建自定义运行时镜像
JDK 9 引入的 jlink 工具可以创建包含应用所需模块的最小运行时镜像,大大减小了部署包的大小。
# 创建自定义运行时镜像
jlink --module-path $JAVA_HOME/jmods:target/modules \
--add-modules com.example.application \
--output app-image \
--strip-debug \
--compress 2 \
--no-header-files \
--no-man-pages
这个命令会创建一个只包含com.example.application模块及其依赖的最小运行时环境,可以显著减小应用的部署大小。
六、工具链的增强
JDK 17 提供了更强大的开发和诊断工具,帮助开发者更高效地编写、调试和优化 Java 应用。
6.1 JShell:交互式 Java 编程(JDK 9)
JShell 是 JDK 9 引入的交互式编程工具,允许开发者无需创建类和方法即可快速测试 Java 代码片段。
$ jshell
| 欢迎使用 JShell -- 版本 17.0.9
| 要大致了解该版本, 请键入: /help intro
jshell> int a = 10
a ==> 10
jshell> int b = 20
b ==> 20
jshell> a + b
$3 ==> 30
jshell> String.format("a + b = %d", a + b)
$4 ==> "a + b = 30"
jshell> import java.util.List
jshell> List<String> list = List.of("Java", "JDK", "17")
list ==> [Java, JDK, 17]
jshell> list.stream().filter(s -> s.startsWith("J")).count()
$7 ==> 2
jshell> /exit
| 再见
JShell 的主要用途:
- 快速测试代码片段
- 学习 Java API
- 原型开发
- 教学和演示
6.2 jlink:创建自定义运行时镜像(JDK 9)
如前所述,jlink 工具允许创建只包含应用所需模块的自定义运行时镜像,这对于减小部署大小和提高安全性非常有用。
6.3 jdeprscan:检测已过时的 API(JDK 9)
jdeprscan 工具可以扫描应用代码,检测使用的已过时 API,帮助开发者准备升级。
# 扫描JAR文件中的过时API使用
jdeprscan --class-path myapp.jar
# 扫描运行时镜像中的过时API使用
jdeprscan --image app-image
6.4 增强的 jstack 和 jmap
JDK 17 中的 jstack 和 jmap 工具提供了更多信息和选项,特别是在诊断并发问题和内存泄漏方面。
# 打印进程的线程栈信息,包括锁信息
jstack -l <pid>
# 生成堆转储
jmap -dump:format=b,file=heapdump.hprof <pid>
# 分析堆使用情况
jmap -histo <pid>
七、移除和废弃的功能
JDK 17 移除和废弃了一些过时的功能,这些变化虽然可能需要一些迁移工作,但长远来看使平台更加现代化和安全。
7.1 移除的功能
-
移除了永久代(PermGen):JDK 8 已经用元空间(Metaspace)替代了永久代,但相关的 JVM 参数在 JDK 17 中被彻底移除。
-
移除了遗留的 GC 算法:如 CMS(Concurrent Mark Sweep)垃圾回收器在 JDK 14 中被废弃,JDK 17 中完全移除。
-
移除了一些过时的 API:如
sun.misc包中的许多类,Thread.stop()等不安全的方法。 -
移除了 Java EE 和 CORBA 模块:这些模块在 JDK 9 中被标记为 deprecated for removal,JDK 11 中正式移除。
7.2 废弃的功能
-
废弃了 Security Manager:JDK 17 中标记为废弃,计划在未来版本中移除,推荐使用其他安全机制替代。
-
废弃了一些加密算法:如 MD5 和 SHA-1 在证书验证中的使用被进一步限制。
-
废弃了一些命令行工具:如 jhat 被标记为废弃,推荐使用 VisualVM 等替代工具。
八、升级到 JDK 17 的最佳实践
升级到 JDK 17 是一个重要决策,需要谨慎规划和执行。以下是一些最佳实践:
8.1 升级前的准备工作
-
评估依赖兼容性:检查项目依赖是否支持 JDK 17,特别是一些老旧的库可能需要升级。
-
使用工具检测问题:
- jdeprscan:检测过时 API 的使用
- jdeps:分析依赖关系
- IDE 内置的兼容性检查工具
-
制定迁移计划:根据评估结果,制定分阶段的迁移计划,优先解决阻塞性问题。
8.2 升级步骤
-
先升级到最新的 JDK 8 版本:确保在升级到 JDK 17 之前,应用能在最新的 JDK 8 上正常运行。
-
逐步升级:可以先升级到 JDK 11(另一个 LTS 版本),再升级到 JDK 17,减少一次性迁移的风险。
-
启用预览功能(可选):如果想使用最新的预览功能,可以通过
--enable-preview选项启用。 -
测试:在升级后进行全面测试,包括单元测试、集成测试和性能测试。
-
监控:在生产环境部署后,密切监控应用性能和稳定性,特别是内存使用和 GC 行为。
8.3 常见问题及解决方案
-
依赖不兼容:升级依赖库到支持 JDK 17 的版本,或寻找替代库。
-
反射问题:模块化系统可能导致反射失败,需要在 module-info.java 中使用
opens指令开放相关包。 -
性能问题:新的 GC 可能需要调整参数才能达到最佳性能,可以参考官方文档进行调优。
-
安全设置问题:更严格的安全设置可能导致某些功能失败,需要根据实际情况调整安全策略。
九、总结:为什么现在就应该升级到 JDK 17
JDK 17 作为继 JDK 8 之后最重要的 LTS 版本,汇集了九年的技术创新和改进,带来了以下显著优势:
-
更简洁的代码:var、文本块、记录类等特性减少了模板代码,提高了可读性。
-
更高的性能:新一代 GC、JIT 优化和启动时间改进使应用运行更快、更高效。
-
更强的安全性:现代加密算法、更严格的默认设置和模块化系统提高了应用的安全性。
-
更丰富的 API:新增的集合方法、日期时间功能和 CompletableFuture 增强使开发更高效。
-
更长的支持周期:作为 LTS 版本,JDK 17 将获得至少 8 年的支持,比 JDK 8 更长久。
-
更好的资源利用:模块化系统和 jlink 工具使应用更小、更高效。
升级到 JDK 17 可能需要一些前期工作,但带来的长期收益是显著的。无论是新应用还是现有应用,现在都是考虑迁移到 JDK 17 的最佳时机。


被折叠的 条评论
为什么被折叠?



