从 JDK 8 到 JDK 17:Java 的九年飞跃,你不可不知的 50 + 项重大改进

引言:为什么升级 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 在应用启动时间上有显著改进,这主要得益于:

  1. AppCDS(应用类数据共享)的增强:允许将应用类和依赖库的类数据预编译并缓存,减少启动时的类加载和验证时间。

  2. 提前编译(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 收紧了多项安全默认设置,减少了潜在的安全风险:

  1. 默认禁用不安全的 SSL/TLS 协议:如 SSLv3、TLSv1 和 TLSv1.1
  2. 增强的证书验证:更严格的证书链验证规则
  3. 限制序列化:默认禁用某些危险的序列化操作
  4. 增强的访问控制:更严格的模块访问控制

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 模块化应用的优势

  1. 更好的封装性:只有明确导出的包才能被其他模块访问
  2. 减少内存占用:可以只加载应用需要的模块
  3. 更强的安全性:模块间的访问受到严格控制
  4. 更清晰的依赖关系:显式声明的依赖关系使系统结构更清晰
  5. 可定制的运行时镜像:可以使用 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 移除的功能

  1. 移除了永久代(PermGen):JDK 8 已经用元空间(Metaspace)替代了永久代,但相关的 JVM 参数在 JDK 17 中被彻底移除。

  2. 移除了遗留的 GC 算法:如 CMS(Concurrent Mark Sweep)垃圾回收器在 JDK 14 中被废弃,JDK 17 中完全移除。

  3. 移除了一些过时的 API:如sun.misc包中的许多类,Thread.stop()等不安全的方法。

  4. 移除了 Java EE 和 CORBA 模块:这些模块在 JDK 9 中被标记为 deprecated for removal,JDK 11 中正式移除。

7.2 废弃的功能

  1. 废弃了 Security Manager:JDK 17 中标记为废弃,计划在未来版本中移除,推荐使用其他安全机制替代。

  2. 废弃了一些加密算法:如 MD5 和 SHA-1 在证书验证中的使用被进一步限制。

  3. 废弃了一些命令行工具:如 jhat 被标记为废弃,推荐使用 VisualVM 等替代工具。

八、升级到 JDK 17 的最佳实践

升级到 JDK 17 是一个重要决策,需要谨慎规划和执行。以下是一些最佳实践:

8.1 升级前的准备工作

  1. 评估依赖兼容性:检查项目依赖是否支持 JDK 17,特别是一些老旧的库可能需要升级。

  2. 使用工具检测问题

    • jdeprscan:检测过时 API 的使用
    • jdeps:分析依赖关系
    • IDE 内置的兼容性检查工具
  3. 制定迁移计划:根据评估结果,制定分阶段的迁移计划,优先解决阻塞性问题。

8.2 升级步骤

  1. 先升级到最新的 JDK 8 版本:确保在升级到 JDK 17 之前,应用能在最新的 JDK 8 上正常运行。

  2. 逐步升级:可以先升级到 JDK 11(另一个 LTS 版本),再升级到 JDK 17,减少一次性迁移的风险。

  3. 启用预览功能(可选):如果想使用最新的预览功能,可以通过--enable-preview选项启用。

  4. 测试:在升级后进行全面测试,包括单元测试、集成测试和性能测试。

  5. 监控:在生产环境部署后,密切监控应用性能和稳定性,特别是内存使用和 GC 行为。

8.3 常见问题及解决方案

  1. 依赖不兼容:升级依赖库到支持 JDK 17 的版本,或寻找替代库。

  2. 反射问题:模块化系统可能导致反射失败,需要在 module-info.java 中使用opens指令开放相关包。

  3. 性能问题:新的 GC 可能需要调整参数才能达到最佳性能,可以参考官方文档进行调优。

  4. 安全设置问题:更严格的安全设置可能导致某些功能失败,需要根据实际情况调整安全策略。

九、总结:为什么现在就应该升级到 JDK 17

JDK 17 作为继 JDK 8 之后最重要的 LTS 版本,汇集了九年的技术创新和改进,带来了以下显著优势:

  1. 更简洁的代码:var、文本块、记录类等特性减少了模板代码,提高了可读性。

  2. 更高的性能:新一代 GC、JIT 优化和启动时间改进使应用运行更快、更高效。

  3. 更强的安全性:现代加密算法、更严格的默认设置和模块化系统提高了应用的安全性。

  4. 更丰富的 API:新增的集合方法、日期时间功能和 CompletableFuture 增强使开发更高效。

  5. 更长的支持周期:作为 LTS 版本,JDK 17 将获得至少 8 年的支持,比 JDK 8 更长久。

  6. 更好的资源利用:模块化系统和 jlink 工具使应用更小、更高效。

升级到 JDK 17 可能需要一些前期工作,但带来的长期收益是显著的。无论是新应用还是现有应用,现在都是考虑迁移到 JDK 17 的最佳时机。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值