你好,我是看山。
本文收录在 《从小工到专家的 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
关键字,用来返回值。
yield
与return
的区别在于,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.ServerSocket
和java.net.Socket
,ServerSocket
用来监听连接请求的端口,连接成功后返回的是Socket
对象,可以通过操作Socket
对象实现数据发送和读取。Java 是通过SocketImpl
实现这些功能。
在 Java13 之前,通过SocketImpl
的子类PlainSocketImpl
实现。在 Java13 中,引入NioSocketImpl
实现,该实现以 NIO 为基础,与高速缓冲机制集成,实现非阻塞式网络。
如果想用回PlainSocketImpl
,可以设置启动参数-Djdk.net.usePlainSocketImpl=true
即可。
文末总结
本文介绍了 Java13 新增的特性,完整的特性清单可以从https://openjdk.java.net/projects/jdk/13/查看。后续内容会发布在 从小工到专家的 Java 进阶之旅 系列专栏中。
青山不改,绿水长流,我们下次见。
推荐阅读
- 从小工到专家的 Java 进阶之旅
- 一文掌握 Java8 Stream 中 Collectors 的 24 个操作
- 一文掌握 Java8 的 Optional 的 6 种操作
- 使用 Lambda 表达式实现超强的排序功能
- Java8 的时间库(1):介绍 Java8 中的时间类及常用 API
- Java8 的时间库(2):Date 与 LocalDate 或 LocalDateTime 互相转换
- Java8 的时间库(3):开始使用 Java8 中的时间类
- Java8 的时间库(4):检查日期字符串是否合法
- Java8 的新特性
- Java9 的新特性
- Java10 的新特性
- Java11 中基于嵌套关系的访问控制优化
- Java11 的新特性
- Java12 的新特性
- Java13 的新特性
- Java14 的新特性
- Java15 的新特性
- Java16 的新特性
- Java17 的新特性
- Java18 的新特性
- Java19 的新特性
- Java20 的新特性
- Java21 的新特性
- Java22 的新特性
- Java23 的新特性
- Java24 的新特性
你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。
个人主页:https://www.howardliu.cn
个人博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java13 的新特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java13 的新特性