Java 13 by Alena Penkova
自从 Oracle 调整了 Java 的版本发布节奏之后,Java 版本发布越来越快,虽然都说 Java 版本任他发,我用 Java 8,不过新版本的 Java 功能还是要学习一下的。
Java 13 早在 2019 年 9 月就已经发布,虽然不是长久支持版本,但是也带来了不少新功能。
Java 13 官方下载:https://jdk.java.net/archive/
Java 13 官方文档:http://openjdk.java.net/projects/jdk/13/
Java 13 新功能:
- JEP 350: 动态 CDS 存档
- JEP 351: ZGC,归还未使用的内存 (实验性)
- JEP 353: 重新实现 Socket API
- JEP 354: Switch 表达式 (二次预览)
- JEP 355: 文本块 (预览)
扩展:此文章属于 Java 新特性教程 系列,会介绍 Java 每个版本的新功能,可以点击浏览。
1. JEP 350 动态 CDS 存档
JVM 启动时有一步是需要在内存中加载类,而如果有多个 jar,加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间,为了减少这个时间,Java 10
引入了应用程序类数据共享(CDS)机制,它可以把你想共享的类共享在程序之间,使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。不过 Java 10 中使用这个功能的步骤比较繁琐。
扩展阅读:Java 10 新功能介绍
而 Java 13 中的 AppCDS,允许 Java 应用在程序执行结束时(如果 JVM 没有崩溃)进行动态存档;存储的内容包括所有加载的应用类型类和使用的类库,这些存储的类库本来并不存在于默认的 CDS 存档中。
使用这个功能非常简单,只需要在程序启动时增加启动参数 。
# ArchiveClassesAtExit,程序结束时动态存档 bin/java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello # SharedArchiveFile,使用指定存档启动 bin/java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello
2. JEP 351: ZGC,归还未使用的内存 (实验性)
在 Java 13 之前,ZGC 虽然在清理内存时导致的停顿时间非常少,但是即使内存已经长时间没有使用,ZGC 也不会将内存返还给操作系统,这对那些十分关注内存占用的应用程序非常不友好。
比如:
- 资源按使用量付费的云上容器环境。
- 应用虽然长时间闲置,但是占用了内存,导致运行的其他程序内存紧张。
而新增的这个功能,可以让 ZGC 归还长时间没有使用的内存给操作系统,这对某些用户来说十分友好。
3. JEP 353: 重新实现 Socket API
java.net.Socket
和 java.net.ServerSocket
类早在 Java 1.0 时就已经引入了,它们的实现的 Java 代码和 C 语言代码的混合,维护和调试都十分不易;而且这个实现还存在并发问题,有时候排查起来也很困难。
因此,在 Java 13 中引入了新的实现方式,使用了新的实现 NioSocketImpl
来代替老旧的 PlainSocketImpl
实现。虽然功能相同,但是老的方式在当前以及未来几个版本内不会删除,用户随时可以通过 -Djdk.net.usePlainSocketImpl
参数切换回老的实现方式,以兼容意外情况。
编写一个测试类可以发现这个变化。
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Test { public static void main(String[] args) { try (ServerSocket serverSocket = new ServerSocket(8000)){ boolean running = true; while(running){ Socket clientSocket = serverSocket.accept(); //do something with clientSocket } } catch (IOException e) { e.printStackTrace(); } } }
使用 Java 13 运行,通过参数 -XX:+TraceClassLoading
追踪加载的类,日志中可以看到 NioSocketImpl
。
➜ develop ./jdk-13.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl [0.699s][info ][class,load] java.net.SocketImpl source: jrt:/java.base [0.699s][info ][class,load] java.net.SocketImpl$$Lambda$173/0x0000000800c37440 source: java.net.SocketImpl [0.702s][info ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base [0.702s][info ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base [0.713s][info ][class,load] sun.nio.ch.NioSocketImpl$FileDescriptorCloser source: jrt:/java.base
但在 Java 12 并不是 NioSocketImpl
。
➜ develop ./jdk-12.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl [0.665s][info ][class,load] java.net.SocketImpl source: jrt:/java.base [0.665s][info ][class,load] java.net.AbstractPlainSocketImpl source: jrt:/java.base [0.665s][info ][class,load] java.net.PlainSocketImpl source: jrt:/java.base [0.665s][info ][class,load] java.net.SocksSocketImpl source: jrt:/java.base [0.666s][info ][class,load] java.net.AbstractPlainSocketImpl$1 source: jrt:/java.base
4. JEP 354: Switch 表达式 (二次预览)
你为什么不愿意使用使用 switch
表达式?我想其中一个原因应该是, switch
表达式的代码不够美观优雅,甚至有些啰嗦。比如像下面的例子。
package com.wdbyte; public class Java13Switch { public static void main(String[] args) { //通过传入月份,输出月份所属的季节 System.out.println(switchJava12Before("march")); } public static String switchJava12Before(String month) { String reuslt = null; switch (month) { case "march": case "april": case "may": reuslt = "春天"; break; case "june": case "july": case "august": reuslt = "夏天"; break; case "september": case "october": case "november": reuslt = "秋天"; break; case "december": case "january": case "february": reuslt = "冬天"; break; } return reuslt; } }
而在 Java 12 中,已经对 switch
进行了改进,使之可以使用 cast L ->
表达式进行操作,且可以具有返回值,这样代码就更加美观使用了,不过这在 Java 12 中是一个预览功能。
// 通过传入月份,输出月份所属的季节 public static String switchJava12(String month) { return switch (month) { case "march", "april", "may" -> "春天"; case "june", "july", "august" -> "夏天"; case "september", "october", "november" -> "秋天"; case "december", "january", "february" -> "冬天"; default -> "month erro"; }; }
扩展阅读:Java 12 新特性介绍
而现在,在 Java 13 中,又对 switch
表达式进行了增强,增加了yield
关键词用于返回值,相比 break
,语义更加明确了。
public static String switchJava13(String month) { return switch (month) { case "march", "april", "may": yield "春天"; case "june", "july", "august": yield "夏天"; case "september", "october", "november": yield "秋天"; case "december", "january", "february": yield "冬天"; default: yield "month error"; }; }
5. JEP 355: 文本块 (预览)
在这之前,如果我们把一个 JSON 赋值给字符串:
String content = "{\n" + " \"upperSummary\": null,\n" + " \"sensitiveTypeList\": null,\n" + " \"gmtModified\": \"2011-08-05 10:50:09\",\n" + " \"lowerGraph\": null,\n" + " \"signature\": \"\",\n" + " \"appName\": \"xxx\",\n" + " \"lowerSummary\": null,\n" + " \"gmtCreate\": \"2011-08-05 10:50:09\",\n" + " \"type\": \"CALL\",\n" + " \"name\": \"xxxx\",\n" + " \"subType\": \"yyy\",\n" + " \"id\": 1,\n" + " \"projectId\": 1,\n" + " \"status\": 1\n" + "}";
终于不用写丑陋的长字符串了,从 Java 13 开始你可以使用文本块的方式定义字符串了。
String content2 = """ { "upperSummary": null, "sensitiveTypeList": null, "gmtModified": "2011-08-05 10:50:09", "lowerGraph": null, "signature": "", "appName": "xxx", "lowerSummary": null, "gmtCreate": "2011-08-05 10:50:09", "type": "CALL", "name": "xxxx", "subType": "yyy", "id": 1, "projectId": 1, "status": 1 } """;
不过这是一个预览功能,如果你要是在 Java 13 中使用需要手动开启预览功能,这个功能在 Java 15 中正式发布。
如果你想尝试,可以去下载最新版了,玩的开心。