Java13 的新特性

你好,我是看山。

本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。

从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证新特性,推动 Java 的发展。从 《JVM Ecosystem Report 2021》 中可以看出,目前开发环境中有近半的环境使用 Java8,有近半的人转移到了 Java11,随着 Java17 的发布,相信比例会有所变化。

因此,准备出一个系列,配合示例讲解,阐述各个版本的新特性。

概述

本文讲解一下 Java13 的特性,这个版本在语法特性上增加不多,值得关注的是两个预览功能:Switch 表达式和文本块,另外可以关乎的是性能优化方面的:动态类数据共享(CDS)存档、ZGC 动态释放未使用内存、Socket API 重构。这些方面可以看出,Java 的升级方向有两个,一是增加功能,增加新的语法特性;二是增强功能,提升已有功能性能。

预览功能

Java13 引入了两个新的语法特性:Switch 表达式和文本块。这些预览功能是为了让开发者尝鲜的同时,可以快速调整,反馈好就留下,不好就移除。目前来看,这些特性还是挺香的。

真香定律

Switch 表达式

在 Java12 中 Switch 表达式首次以预览版的身份出现,在 Java13 中又做了增强,在 Java14 正式提供。Java13 添加了yield关键字,用来返回值。

yieldreturn的区别在于,yield只会跳出switch块,return是跳出当前方法或循环。

比如下面的例子,在 Java12 之前,要判断日期可以这样写:

@Test
void testSwitch() {
    final DayOfWeek day = DayOfWeek.from(LocalDate.now());
    String typeOfDay = "";
    switch (day) {
        case MONDAY:
        case TUESDAY:
        case WEDNESDAY:
        case THURSDAY:
        case FRIDAY:
            typeOfDay = "Working Day";
            break;
        case SATURDAY:
        case SUNDAY:
            typeOfDay = "Rest Day";
            break;
    }

    Assertions.assertFalse(typeOfDay.isEmpty());
}

在 Java12 提供的 Switch 表达式预览功能,我们可以简化一下:

@Test
void testSwitchExpression() {
    final DayOfWeek day = DayOfWeek.SATURDAY;
    final String typeOfDay = switch (day) {
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Working Day";
        case SATURDAY, SUNDAY -> "Day Off";
    };

    Assertions.assertEquals("Day Off", typeOfDay);
}

这样可以实现判断,但是没有办法在表达式中实现其他逻辑了。于是 Java13 补齐了这个功能:

@Test
void testSwitchExpression13() {
    final DayOfWeek day = DayOfWeek.SATURDAY;
    final String typeOfDay = switch (day) {
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> {
            System.out.println("Working Day: " + day);
            yield "Working Day";
        }
        case SATURDAY, SUNDAY -> {
            System.out.println("Day Off: " + day);
            yield "Day Off";
        }
    };

    Assertions.assertEquals("Day Off", typeOfDay);
}

这里需要说明一下,既然是预览功能,会与正式提供功能有些出入。上面的代码是在 Java14 环境中编写,与 Java13 发布的功能描述有些差异,这点不必深究,已经废弃的约束就是不存在。

文本块

一直以来,Java 中的字符串定义都是以双引号括起来的形式,不支持多行书写,所以在需要多行字符串中,需要使用转义符表示,既不好看、还不好读,更不好维护。

千呼万唤始出来,终于有了文本块功能。

比如,我们想要写一段 Json 格式的数据,Java13 之前需要写成:

String json = "{\n"
        + "  \"wechat\": \"hellokanshan\",\n"
        + "  \"wechatName\": \"看山、",\n"
        + "  \"mp\": \"kanshanshuo\",\n"
        + "  \"mpName\": \"看山的小屋、"\n"
        + "}\n";

但是在 Java13 预览版中可以写作:

String json2 = """
        {
          "wechat": "hellokanshan",
          "wechatName": "看山",
          "mp": "kanshanshuo",
          "mpName": "看山的小屋"
        }
        """;

少了很多的+、换行、转移等字符,看着更加直观。

这个功能在 Java15 中正式提供。

动态类数据共享(CDS)存档

CDS 是 Java5 引入的一种类预处理方式,可以将一组类共享到一个归档文件中,借助内存映射加载类数据,减少启动时间,并可实现在多 JVM 之间共享的功能。在 Java10 对其进行扩展,增大了 CDS 使用范围,即 AppCDS(参见 Java10 新特性)。到了 Java12,将 CDS 归档文件作为了默认功能开放出来(参见 Java12 新特性)。

但是这个功能在使用的时候还是有些麻烦。为了生成归档文件,开发人员必须先对应用程序进行试运行,创建一个类列表,然后将其转储到归档文件中。然后,这个归档才可以用来在 JVM 之间共享元数据。

Java13 简化了这个过程:允许 Java 应用在运行结束后动态归档,即将已被加载但不属于 CDS 的类(包括自定义类和引用库的类)动态添加到 CDS 归档文件中。不用再提供归档类的列表,通过更加简洁的方式创建包含应用程序的归档。

我们可以使用-XX:ArchiveClassesAtExit参数控制应用程序退出时创建 CDS 归档文件:

java -XX:ArchiveClassesAtExit=<archive filename> -cp <app jar> AppName

也可以使用-XX:SharedArchiveFile来使用动态存档功能:

java -XX:SharedArchiveFile=<archive filename> -cp <app jar> AppName

ZGC 增强:释放未使用内存

ZGC 是 Java11 中引入的一个可伸缩、低延迟的垃圾收集器,主要目标包括:GC 停顿时间不超过 10ms;可以处理从几百 MB 的小堆,到几个 TB 的大堆;应用吞吐能力不会下降超过 15%等(参见 Java11 的新特性)。

但是 ZGC 并没有像 Hotspot 中的 G1 和 Shenandoah 那样,可以主动释放未使用的内存,对于多数应用程序来说,CPU 和内存都是稀缺资源,尤其是现在云上环境和虚拟化技术,如果应用程序占用的内存长期处于空闲状态,还紧握住不释放,就是极大的浪费。

在 Java13 中对其进行改进,包括:

  • 可释放空闲内存
  • 支持的最大堆大小从 4TB 扩大到 16TB

我们来看下 ZGC 的内部逻辑。

ZGC 堆由一组称为 ZPages 的堆区域组成,每个 ZPage 都与提交的堆内存的可变数量相关联。当 ZGC 压缩堆时,ZPages 被释放并插入到页面缓存 ZPageCache 中,页面缓存中的 ZPages 可以重新使用,以满足新的堆分配。

ZPageCache 中的 ZPages 集合代表堆中未使用的部分,这部分可以释放回操作系统。ZPageCache 中的 ZPages 根据 LRU(最近最少使用)排序,并按照大中小进行分组。这样的话就可以根据算法按顺序释放未使用的内存。

Java13 还提供了-XX:ZUncommitDelay=<seconds>命令,用于指定释放多长时间(默认是 5 分钟)未使用的内存,这个参数类似于 Shenandoah 中的-XX:ShenandoahUncommitDelay=<milliseconds>

在 Java13 中,ZGC 内存释放功能默认开启,可通过参数-XX:-ZUncommit关闭该功能。由于 ZGC 释放内存时,不会低于最小堆内存,即当最小堆内存(-Xms)与最大堆内存(-Xmx)一样时,不会自动释放。

Socket API 重构

Java 中的 Socket 是从 Java1.0 开始就有的,是 Java 中不可或缺的网络 API,算起来已经服役 20 多年了。在这段时间内,信息技术已经发生了很多变化,这些上古 API 有一定的局限性,而且不容易维护和调试。

Java 的 Socket API 主要包括java.net.ServerSocketjava.net.SocketServerSocket用来监听连接请求的端口,连接成功后返回的是Socket对象,可以通过操作Socket对象实现数据发送和读取。Java 是通过SocketImpl实现这些功能。

在 Java13 之前,通过SocketImpl的子类PlainSocketImpl实现。在 Java13 中,引入NioSocketImpl实现,该实现以 NIO 为基础,与高速缓冲机制集成,实现非阻塞式网络。

如果想用回PlainSocketImpl,可以设置启动参数-Djdk.net.usePlainSocketImpl=true即可。

文末总结

本文介绍了 Java13 新增的特性,完整的特性清单可以从https://openjdk.java.net/projects/jdk/13/查看。后续内容会发布在 从小工到专家的 Java 进阶之旅 系列专栏中。

青山不改,绿水长流,我们下次见。

推荐阅读


你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。

个人主页:https://www.howardliu.cn
个人博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java13 的新特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java13 的新特性

👇🏻欢迎关注我的公众号「看山的小屋」,领取精选资料👇🏻

公众号:看山的小屋