Skip to content
标签
基础
字数
14992 字
阅读时间
60 分钟

Jdk15

运行JDK15需要IDEA 2020.2才能支持

自动匹配

java

public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return Objects.equals(name, student.name);
}

// 简写为
//jdk14需通过 --enable-preview --source 14  选项开启
// instanceof运算符 赋值的变量会根据绑定变量的表达式和予以确定。
public boolean equals(Object obj) {
    // 如果obj是Student的实例,则将其强制转换为Student并分配给绑定变量s。绑定变量在if语句的true块中,而不在if语句的false块中。
    return (obj instanceof Student s) && Objects.equals(this.name, s.name);
}

文本块功能

Text Blocks首次是在JDK 13中以预览功能出现的,然后在JDK 14中又预览了一次,终于在JDK 15中被确定下来。

文本块建议的目标是提高 Java 程序中的字符串的可读性。

之前引入html、xml、sql时一般需要通过“”进行转义,转义后的内容难以阅读和维护。

文本块的开头定界符是由三个双引号 """ 开始,从新的一行开始字符串的内容。这里的新起的这行不属于字符串,只表示内容开始,是语法的一部分。以 """ 结束。 """ 可以紧跟字符串内容,也可以另起一行。另起一行时,字符串内容最后会留有一新行。

java编译器会自动删除不需要的缩进:

  • 每行结尾的空格都会删除
  • 每行开始的共有的空格会自动删除
  • 只保留相对缩进。

新行 """ 结束时,将 """ 向左调整,则可以给所有行前加相应数量的空格。将 """ 向右调整,没有作用。

java
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";
//文本块
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;
    
"""
line 1
line 2
line 3"""
// 相当于
"line 1\nline 2\nline 3"

Records(二次预览)

Records Class 也是第二次出现的预览功能,它在 JDK 14 中也出现过一次了,使用 Record 可以更方便的创建一个常量类,通过record增强Java编程语言。record提供了一种紧凑的语法来声明类,这些类是浅层不可变数据的透明持有者。

record在语义上是数据的简单透明持有者,所以记录会自动获取很多标准成员:

  • 状态声明中的每个成员,都有一个 private final的字段;
  • 状态声明中的每个组件的公共读取访问方法,该方法和组件具有相同的名字;
  • 一个公共的构造函数,其签名与状态声明相同;
  • equals和hashCode的实现;
  • toString的实现。
java
record People(String name, int age) { }

Compiled from "RecordDemo01.java"
final class People extends java.lang.Record {
  public People(java.lang.String, int);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public java.lang.String name();
  public int age();
}

records不能扩展任何类,并且不能声明私有字段以外的实例字段。声明的任何其他字段都必须是静态的。

records类都是隐含的final类,并且不能是抽象类。这些限制使得records的API仅由其状态描述定义,并且以后不能被其他类实现或继承。

在record中额外声明变量

也可以显式声明从状态描述自动派生的任何成员。可以在没有正式参数列表的情况下声明构造函数(这种情况下,假定与状态描述相同),并且在正常构造函数主体正常完成时调用隐式初始化(this.x=x)。这样就可以在显式构造函数中仅执行其参数的验证等逻辑,并省略字段的初始化,例如:

java
record Test(int x, int y) {
  public Test {
     if (x > y)  
      	System.out.println("第一个参数大于第二个参数~");
 	 }
}

密封的类和接口(预览)

通过密封的类和接口来增强 Java 编程语言,这是新的预览特性。Sealed 密封用于限制父类的使用,密封的类和接口可以限制被谁继承和实现。

引入了sealed class或interfaces,这些class或者interfaces只允许被指定的类或者interface进行继承和实现。使用修饰符sealed,您可以将一个类声明为密封类。密封的类使用关键字permits列出可以直接扩展它的类。子类可以是最终的,非密封的或密封的。

java
/**
 目标: JEP 360:Sealed Classes(Preview)密封的类和接口(预览)
 */
public sealed class Person permits Teacher , Student , SportMan , Doctor{ } //人类

final class Teacher extends Person { } //老师

sealed class Student extends Person permits MiddleSchoolStudent , UniversityStudent   { } //学生
final class MiddleSchoolStudent extends Student { }  //中学生
final class UniversityStudent extends Student { }  //大学生

non-sealed class SportMan extends Person { }  //运动员
non-sealed class Doctor extends Person{ } // 医生

class PingPongMan extends SportMan {
}

隐藏类

定义无法发现且具有有限生命周期的隐藏类,从而提高 JVM 上所有语言的效率。隐藏类是为框架(frameworks)所设计的,隐藏类不能直接被其他类的字节码使用,只能在运行时生成类并通过反射间接使用它们。通常来说基于JVM的很多语言都有动态生成类的机制,这样可以提高语言的灵活性和效率。

  • 隐藏类天生为框架设计的,在运行时生成内部的class。
  • 隐藏类只能通过反射访问,不能直接被其他类的字节码访问。
  • 隐藏类可以独立于其他类加载、卸载,这可以减少框架的内存占用。

Hidden Classes是什么呢? Hidden Classes就是不能直接被其他class的二进制代码使用的class。Hidden Classes主要被一些框架用来生成运行时类,但是这些类不是被用来直接使用的,而是通过反射机制来调用。 比如在JDK8中引入的lambda表达式,JVM并不会在编译的时候将lambda表达式转换成为专门的类,而是在运行时将相应的字节码动态生成相应的类对象。

特性

  • 不可发现性。因为我们是为某些静态的类动态生成的动态类,所以我们希望把这个动态生成的类看做是静态类的一部分。所以我们不希望除了该静态类之外的其他机制发现。
  • 访问控制。我们希望在访问控制静态类的同时,也能控制到动态生成的类。
  • 生命周期。动态生成类的生命周期一般都比较短,我们并不需要将其保存和静态类的生命周期一致。

API支持

  • java.lang.reflect.Proxy可以定义隐藏类作为实现代理接口的代理类。 java.lang.invoke.StringConcatFactory可以生成隐藏类来保存常量连接方法; java.lang.invoke.LambdaMetaFactory可以生成隐藏的nestmate类,以容纳访问封闭变量的lambda主体;

普通类是通过调用ClassLoader::defineClass创建的,而隐藏类是通过调用Lookup::defineHiddenClass创建的。这使JVM从提供的字节中派生一个隐藏类,链接该隐藏类,并返回提供对隐藏类的反射访问的查找对象。调用程序可以通过返回的查找对象来获取隐藏类的Class对象。

ZGC可扩展的低延迟垃圾收集器

ZGC是Java 11引入的新的垃圾收集器(JDK9以后默认的垃圾回收器是G1),经过了多个实验阶段,自此终于成为正式特性。 自 2018 年以来,ZGC 已增加了许多改进,从并发类卸载、取消使用未使用的内存、对类数据共享的支持到改进的 NUMA 感知。此外,最大堆大小从 4 TB 增加到 16 TB。支持的平台包括 Linux、Windows 和 MacOS。

ZGC是一个重新设计的并发的垃圾回收器,通过减少 GC 停顿时间来提高性能。

但是这并不是替换默认的GC,默认的GC仍然还是G1;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseZGC来启用ZGC,现在只需要-XX:+UseZGC就可以。相信不久的将来它必将成为默认的垃圾回收器。

相关的参数有ZAllocationSpikeTolerance、ZCollectionInterval、ZFragmentationLimit、ZMarkStackSpaceLimit、ZProactive、ZUncommit、ZUncommitDelay ZGC-specific JFR events(ZAllocationStall、ZPageAllocation、ZPageCacheFlush、ZRelocationSet、ZRelocationSetGroup、ZUncommit)也从experimental变为product

EdDSA 数字签名算法

新加入基于Edwards-Curve数字签名算法(EdDSA-Edwards-Curve Digital Signature Algorithm)的加密签名,即爱德华兹曲线数字签名算法。

在许多其它加密库(如 OpenSSL 和 BoringSSL)中得到支持。

与 JDK 中的现有签名方案相比,EdDSA 具有更高的安全性和性能,因此备受关注。它已经在OpenSSL和BoringSSL等加密库中得到支持,在区块链领域用的比较多。

EdDSA是一种现代的椭圆曲线方案,具有JDK中现有签名方案的优点。EdDSA将只在SunEC提供商中实现。

重新实现 DatagramSocket API

新的计划是JEP 353的后续,该方案重新实现了遗留的套接字API。 java.net.datagram.Socket和java.net.MulticastSocket的当前实现可以追溯到JDK 1.0,那时IPv6还在开发中。因此,当前的多播套接字实现尝试调和IPv4和IPv6难以维护的方式。

通过替换 java.net.datagram 的基础实现,重新实现旧版 DatagramSocket API。 更改java.net.DatagramSocket 和 java.net.MulticastSocket 为更加简单、现代化的底层实现。提高了 JDK 的可维护性和稳定性。

通过将java.net.datagram.Socket和java.net.MulticastSocket API的底层实现替换为更简单、更现代的实现来重新实现遗留的DatagramSocket API。 新的实现:1.易于调试和维护;2.与Project Loom中正在探索的虚拟线程协同。

禁用偏向锁定

在默认情况下禁用偏向锁定,并弃用所有相关命令行选项。目标是确定是否需要继续支持偏置锁定的高维护成本的遗留同步优化,HotSpot虚拟机使用该优化来减少非竞争锁定的开销。尽管某些Java应用程序在禁用偏向锁后可能会出现性能下降,但偏向锁的性能提高通常不像以前那么明显。

该特性默认禁用了biased locking(-XX:+UseBiasedLocking),并且废弃了所有相关的命令行选型(BiasedLockingStartupDelay, BiasedLockingBulkRebiasThreshold, BiasedLockingBulkRevokeThreshold, BiasedLockingDecayTime, UseOptoBiasInlining, PrintBiasedLockingStatistics and PrintPreciseBiasedLockingStatistics)

Shenandoah 垃圾回收算法转正

Shenandoah垃圾回收算法终于从实验特性转变为产品特性,这是一个从 JDK 12 引入的回收算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。

怎么形容Shenandoah和ZGC的关系呢?异同点大概如下: 相同点:性能几乎可认为是相同的 不同点:ZGC是Oracle JDK的,根正苗红。而Shenandoah只存在于OpenJDK中,因此使用时需注意你的JDK版本

打开方式:使用-XX:+UseShenandoahGC命令行参数打开。

Shenandoah在JDK12被作为experimental引入,在JDK15变为Production;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC来启用,现在只需要-XX:+UseShenandoahGC即可启用

外部存储器访问 API(二次孵化)

引入一个 API,以允许 Java 程序安全、有效地访问 Java 堆之外的外部存储器。如本机、持久和托管堆。

有许多Java程序是访问外部内存的,比如Ignite和MapDB。该API将有助于避免与垃圾收集相关的成本以及与跨进程共享内存以及通过将文件映射到内存来序列化和反序列化内存内容相关的不可预测性。该Java API目前没有为访问外部内存提供令人满意的解决方案。但是在新的提议中,API不应该破坏JVM的安全性。

Foreign-Memory Access API在JDK14被作为incubating API引入,在JDK15处于Second Incubator,提供了改进。

移除 Solaris 和 SPARC 端口

删除对Solaris/SPARC、Solaris/x64和Linux/SPARC端口的源代码和构建支持,在JDK 14中被标记为废弃,在JDK15版本正式移除。

许多正在开发的项目和功能(如Valhalla、Loom和Panama)需要进行重大更改以适应CPU架构和操作系统特定代码。

近年来,Solaris 和 SPARC 都已被 Linux 操作系统和英特尔处理器取代。放弃对 Solaris 和 SPARC 端口的支持将使 OpenJDK 社区的贡献者能够加速开发新功能,从而推动平台向前发展。

移除了 Nashorn JavaScript 脚本引擎

Nashorn是在JDK提出的脚本执行引擎,该功能是 2014 年 3 月发布的 JDK 8 的新特性。在JDK11就已经把它标记为废弃了,JDK15完全移除。

在JDK11中取以代之的是GraalVM。GraalVM是一个运行时平台,它支持Java和其他基于Java字节码的语言,但也支持其他语言,如JavaScript,Ruby,Python或LLVM。性能是Nashorn的2倍以上。

JDK15移除了Nashorn JavaScript Engine及jjs 命令行工具。具体就是jdk.scripting.nashorn及jdk.scripting.nashorn.shell这两个模块被移除了。

补充:

Graal VM在HotSpot VM基础上增强而成的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用。语言包括:Java、Scala、Groovy、Kotlin;C、C++、JavaScript、Ruby、Python、R等

废除 RMI 激活以便在将来进行删除

RMI Activation被标记为Deprecate,将会在未来的版本中删除。RMI激活机制是RMI中一个过时的部分,自Java 8以来一直是可选的而非必选项。RMI激活机制增加了持续的维护负担。RMI的其他部分暂时不会被弃用。

RMI jdk1.2引入,EJB

在RMI系统中,我们使用延迟激活。延迟激活将激活对象推迟到客户第一次使用(即第一次方法调用)之前。 既然RMI Activation这么好用,为什么要废弃呢? 因为对于现代应用程序来说,分布式系统大部分都是基于Web的,web服务器已经解决了穿越防火墙,过滤请求,身份验证和安全性的问题,并且也提供了很多延迟加载的技术。 所以在现代应用程序中,RMI Activation已经很少被使用到了。并且在各种开源的代码库中,也基本上找不到RMI Activation的使用代码了。 为了减少RMI Activation的维护成本,在JDK8中,RMI Activation被置为可选的。现在在JDK15中,终于可以废弃了。

Jdk14

自动匹配(预览,同jdk15)

参考jdk15 自动匹配

switch

java
public void print(int days) {
  // 声明变量score,并为其赋值为'C'
  var score = 'B';
  String result = switch (score) {
          //多值匹配
      case 'A', 'B' -> "上等";
          // 不需要break
      case 'C' -> "中等";
      case 'D', 'E' -> "下等";
          // 当使用箭头标签时,箭头标签右边可以是表达式、throw语句或是代码块。如果是代码块,需要使用yield语句来返回值
      default -> {
          if (score > 100) {
            yield "数据不能超过100";
          } else {
            yield score + "此分数低于0分";
          }
      }
  };
  System.out.println(result);
}

Text Blocks(二次预览)

( JDK 13后的第二个预览版) ,使用同jdk15 文本块共嗯那个

Java打包工具(孵化项目)

jpackage工具将Java应用程序打包到特定于平台的程序包中,该程序包包含所有必需的依赖项。该应用程序可以作为普通JAR文件的集合或作为模块的集合提供。受支持的特定于平台的软件包格式为:

  • Linux:debrpm
  • macOS:pkgdmg
  • Windows:msiexe

默认情况下,jpackage以最适合其运行系统的格式生成软件包。

基本用法:非模块化应用

假设您有一个包含JAR文件的应用程序,所有应用程序都位于一个名为的目录中lib,并且lib/main.jar包含主类。然后命令

$ jpackage --name myapp --input lib --main-jar main.jar

初次运行会报第一个错:

java
WARNING: Using incubator modules: jdk.incubator.jpackage
找不到 WiX 工具 (light.exe, candle.exe)
从 https://wixtoolset.org 下载 WiX 3.0 或更高版本,然后将其添加到 PATH。
错误:类型 [null] 无效或不受支持
解决:我们可以根据提示进入https://wixtoolset.org下载安装就好了,然后类似添加jdk的bin一样,将它的bin目录添加到path

将以本地系统的默认格式打包应用程序,将生成的打包文件保留在当前目录中。如果MANIFEST.MF文件中main.jar没有Main-Class属性,则必须显式指定主类:

$ jpackage --name myapp --input lib --main-jar main.jar \
  --main-class myapp.Main

软件包的名称将为myapp,尽管软件包文件本身的名称将更长,并以软件包类型(例如myapp.exe)结尾。该软件包将包括该应用程序的启动器,也称为myapp。要启动该应用程序,启动程序会将从输入目录复制的每个JAR文件放在JVM的类路径上。

如果您希望以默认格式以外的其他格式制作软件包,请使用该--type选项。例如,要在macOS上生成pkg文件而不是dmg文件:

$ jpackage --name myapp --input lib --main-jar main.jar --type pkg

基本用法:模块化应用

如果您有一个模块化应用程序,该应用程序由目录中的模块化JAR文件和/或JMOD文件组成,并且模块中lib包含主类myapp,则命令

$ jpackage --name myapp --module-path lib -m myapp

将其打包。如果myapp模块未标识其主类,则必须再次明确指定:

$ jpackage --name myapp --module-path lib -m myapp/myapp.Main

(打包模块化JAR或JMOD文件时,可以使用和工具--main-class选项指定主类。)jar``jmod

友好的空指针异常

JEP 358增强了对NullPointerException异常的处理,可以显示详细的信息。这个功能需要通过选项-XX:+ShowCodeDetailsInExceptionMessages启用,如下所示:

sh
java -XX:+ShowCodeDetailsInExceptionMessages Npe

但是对于更复杂的代码,不使用调试器就无法确定哪个变量为空。假设下面的代码中出现了一个NPE:

java
a.b.c.i = 99;

仅仅使用文件名和行数,并不能精确定位到哪个变量为null,是a、b还是c?

访问数组也会发生类似的问题。假设此代码中出现一个NPE:

java
a[i][j][k] = 99;

文件名和行号不能精确指出哪个数组组件为空。是a还是a[i]a[i][j]

一行代码可能包含多个访问路径,每个访问路径都可能是NPE的来源。假设此代码中出现一个NPE:

java
a.i = b.j;

文件名和行号并不能确定哪个对象为空,是a还是b?

NPE也可能在方法调用中传递,看下面的代码:

java
x().y().i = 99;

文件名和行号不能指出哪个方法调用返回null。是x()还是y()?

详细示例

接下来我们来示范一下如下的情况下:a.i = b.j

java
public class NpeDemo01 {
    public static void main(String[] args) {
        A a = new A();
        B b =null;
        System.out.println(a.i == b.j);
    }
}

class A{
    int i;
}
class B{
    int j;
}

直接执行代码

java -XX:+ShowCodeDetailsInExceptionMessages NpeDemo01.java

异常信息

Exception in thread "main" java.lang.NullPointerException: Cannot read field "j" because "<local2>" is null
        at NpeDemo01.main(NpeDemo01.java:5)

Records记录类型 (预览)

同jdk15 Records

非易失性映射字节缓冲区

JDK增加了一种文件映射模式,用于访问非易失性内存。非易失性内存能够持久保持数据,因此可以利用该特性来改进性能。

使用FileChannel API创建引用非易失性内存(non-volatile memory,NVM)的MappedByteBuffer实例。该JEP建议升级MappedByteBuffer以支持对非易失性存储器(NVM)的访问。唯一需要的API更改是FileChannel客户端,以请求映射位于NVM支持的文件系统而不是常规文件存储系统上的文件。对MappedByteBuferAPI的最新更改意味着它支持允许直接内存更新所需的所有行为,并提供更高级别的Java客户端库所需的持久性保证,以实现持久性数据类型(例如,块文件系统,日记日志,持久性对象等)。

1. 初步变更

该JEP使用了Java SE API的两个增强功能:

  • 支持implementation-defined的映射模式
  • MppedByteBuffer::force方法以指定范围

2. 特定于JDK的API更改

  • 通过新模块中的公共API公开新的MapMode枚举值

一个公共扩展枚举ExtendedMapMode将添加到jdk.nio.mapmode程序包:

java
package jdk.nio.mapmode;
. . .
public class ExtendedMapMode {
    private ExtendedMapMode() { }

    public static final MapMode READ_ONLY_SYNC = . . .
    public static final MapMode READ_WRITE_SYNC = . . .
}

在调用FileChannel::map方法创建映射到NVM设备文件上的只读或读写MappedByteBuffer时,可以使用上述的枚举值。如果这些标志在不支持NVM设备文件的平台上传递,程序会抛出UnsupportedOperationException异常。在受支持的平台上,仅当目标FileChannel实例是从通过NVM设备打开的派生文件时,才能传递这些参数。在任何其他情况下,都会抛出IOException异常。

G1的NUMA内存分配优化

通过实现可识别非统一的内存访问(non-uniform memory access,NUMA)的内存分配,提高G1 在大型机器上的性能。该JEP优化了G1在使用NUMA(non-uniform memory access)时的内存分配。与硬件相关,不深入讨论了。

动机

现代的多插槽计算机越来越多地具有非统一的内存访问(non-uniform memory access,NUMA),即内存与每个插槽或内核之间的距离并不相等。插槽之间的内存访问具有不同的性能特征,对更远的插槽的访问通常具有更大的延迟。

并行收集器中通过启动-XX:+UseParallelGC能够感知NUMA,这个功能已经实现了多年了,这有助于提高跨多插槽运行单个JVM的配置的性能。其他HotSpot收集器没有此功能,这意味着他们无法利用这种垂直多路NUMA缩放功能。大型企业应用程序尤其倾向于在多个多插槽上以大堆配置运行,但是它们希望在单个JVM中运行具有可管理性优势。 使用G1收集器的用户越来越多地遇到这种扩展瓶颈。

描述

G1的堆组织为固定大小区域的集合。一个区域通常是一组物理页面,尽管使用大页面(通过 -XX:+UseLargePages)时,多个区域可能组成一个物理页面。

如果指定了+XX:+UseNUMA选项,则在初始化JVM时,区域将平均分布在可用NUMA节点的总数上。

在开始时固定每个区域的NUMA节点有些不灵活,但是可以通过以下增强来缓解。为了为mutator线程分配新的对象,G1可能需要分配一个新的区域。它将通过从NUMA节点中优先选择一个与当前线程绑定的空闲区域来执行此操作,以便将对象保留在新生代的同一NUMA节点上。如果在为变量分配区域的过程中,同一NUMA节点上没有空闲区域,则G1将触发垃圾回收。要评估的另一种想法是,从距离最近的NUMA节点开始,按距离顺序在其他NUMA节点中搜索自由区域。

该特性不会尝试将对象保留在老年代的同一NUMA节点上。

JFR事件流

JFR事件流(JFR Event Streaming) — 公开JDK Flight Recorder (JFR)数据以便持续监控。这有助于简化各种工具和应用对JFR数据的访问,并激励进一步创新

动机

HotSpot VM通过JFR产生的数据点超过500个,但是使用者只能通过解析日志文件的方法使用它们。

用户要想消费这些数据,必须开始一个记录并停止,将内容转储到磁盘上,然后解析记录文件。这对于应用程序分析非常有效,但是监控数据却十分不方便(例如显示动态更新数据的仪表盘)。

与创建记录相关的开销包括:

  • 发出在创建新记录时必须发生的事件
  • 写入事件元数据(例如字段布局)
  • 写入检查点数据(例如堆栈跟踪)
  • 将数据从磁盘存储复制到单独的记录文件

如果有一种方法,可以在不创建新记录的情况下,从磁盘存储库中读取正在记录的数据,就可以避免上述开销。

描述

jdk.jfr模块里的jdk.jfr.consumer包,提供了异步订阅事件的功能。用户可以直接从磁盘存储库读取记录数据,也可以直接从磁盘存储流中读取数据,而无需转储记录文件。可以通过注册处理器(例如lambda函数)与流交互,从而对事件的到达进行响应。

下面的代码展示了事件流的用法。通过RecordingStream来创建事件流,使用onEvent()方法注册对特定事件的处理器,最后启动流即可。FlightRecorder.getFlightRecorder().getEventTypes()方法可以得到全部的事件。下面的代码对所有的事件都注册了处理器,直接把事件输出到控制台。

java
import jdk.jfr.FlightRecorder;
import jdk.jfr.consumer.RecordingStream;

public class JFR {
  public static void main(String[] args) {
    try (var rs = new RecordingStream()) {
      FlightRecorder.getFlightRecorder().getEventTypes()
          .forEach(eventType -> rs.onEvent(eventType.getName(), System.out::println));
      rs.start();
    }
  }
}

下面的命令用来运行上面的代码,其中选项-XX:StartFlightRecording用来启动JFR。

bash
$ java -XX:StartFlightRecording JFR.java

JVM每秒一次将线程本地缓冲区中存储的事件定期刷新到磁盘存储库。 一个单独的线程解析最近的文件,直到写入数据为止,然后将事件推送给订阅者。

外部存储器API(孵化)

引入一个API,以允许Java程序安全有效地访问Java堆之外的外部内存。

动机

许多Java的库都能访问外部存储,例如Ignite、mapDB、memcached及Netty的ByteBuf API。这样可以:

  • 避免垃圾回收相关成本和不可预测性
  • 跨多个进程共享内存
  • 通过将文件映射到内存中来序列化、反序列化内存内容。

但是Java API却没有提供一个令人满意的访问外部内存的解决方案。

当Java程序需要访问堆内存之外的外部内存时,通常有两种方式:

  • java.nio.ByteBufferByteBuffer允许使用allocateDirect()方法在堆内存之外分配内存空间。
  • sun.misc.UnsafeUnsafe中的方法可以直接对内存地址进行操作。

ByteBuffer有自己的限制。首先是ByteBuffer的的大小不能超过2G,其次是内存的释放依靠垃圾回收器。Unsafe的API在使用时是不安全的,风险很高,可能会造成JVM崩溃,另外Unsafe本身是不被支持的API,并不推荐使用。

该JEP引入了一种安全高效的API来访问外部内存地址。目前该API处于孵化状态。相关的API在jdk.incubator.foreign模块的jdk.incubator.foreign包中。该API中有3个重要接口:MemorySegmentMemoryAddressMemoryLayout

虽然也可以使用JNI访问内存,但是与该解决方案相关的固有成本使其在实践中很少适用。整个开发流程很复杂,因为JNI要求开发人员编写和维护C代码段。 JNI本质上也很慢,因为每次访问都需要Java到native的转换。

在访问外部内存时,开发人员面临一个难题:应该使用安全但受限(可能效率较低)的方法(例如ByteBuffer),还是应该放弃安全保证并接受不受支持和危险的Unsafe API?

该JEP引入了受支持的,安全且有效的外部内存访问API。

描述

外部存储器访问API引入了三个主要的接口:MemorySegment,MemoryAddress和MemoryLayout。

可以从多种来源创建内存段,例如本机内存缓冲区,Java数组和字节缓冲区(直接或基于堆)。例如,可以如下创建本机内存段:

MemorySegment接口表示一个连续的内存区域。MemorySegment接口表示的内存分段有空间上和时间上的约束。空间上的约束指的是不能访问所分配内存空间之外的地址;时间上的约束指的是当MemorySegment被关闭之后,不能继续对它进行操作。关闭一个MemorySegment会释放内存。

MemoryAddress接口描述在MemorySegment中的相对位置。通常是用法是通过MemorySegment.baseAddress()方法得到起始地址,再使用offset(long l)方法移到到新的地址。

MemoryLayout接口描述MemorySegment中的内存布局。MemoryLayout使用组合的方式来描述内存布局。

MemorySegment用于对具有给定空间和时间范围的连续内存区域进行建模。可以将MemoryAddress视为段内的偏移量,MemoryLayout是内存段内容的程序描述。

可以从多种来源创建内存段,例如本机内存缓冲区,Java数组和字节缓冲区(直接或基于堆)。例如,可以如下创建本机内存段:

java
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   ...
}

上述代码将创建大小为100字节的,与本机内存缓冲区关联的内存段。

通过获取内存访问var句柄可以取消引用与段关联的内存。这些特殊的var句柄具有至少一个强制访问坐标,类型为MemoryAddress,即发生取消引用的地址。它们是使用MemoryHandles类中的工厂方法获得的。要设置本机段的元素,我们可以使用如下所示的内存访问var句柄:

java
VarHandle intHandle = MemoryHandles.varHandle(int.class);

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   MemoryAddress base = segment.baseAddress();
   for (int i = 0 ; i < 25 ; i++) {
       // 内存片段的起始地址、内存修改位置序号、以及要设置的int类型的值
        intHandle.set(base.offset(i * 4), i);
   }
}

JDK 14的其他新特性

JEP 362: 弃用Solaris和SPARC端口

不建议使用Solaris/SPARC,Solaris/x64和Linux/SPARC端口,以在将来的发行版中删除它们。

动机

放弃对这些端口的支持将使OpenJDK社区中的贡献者能够加速新功能的开发,这些新功能将推动平台向前发展。

JEP 363: 移除CMS垃圾收集器

移除CMS(Concurrent Mark Sweep)垃圾收集器。

动机

在两年多以前的JEP 291中,就已经弃用了CMS收集器,并说明会在以后的发行版中删除,以加快其他垃圾收集器的发展。在这段时间里,我们看到了2个新的垃圾收集器ZGC和Shenandoah的诞生,同时对G1的进一步改进。G1自JDK 6开始便成为CMS的继任者。我们希望以后现有的收集器进一步减少对CMS的需求。

描述

此更改将禁用CMS的编译,删除源代码中gc/cms目录的内容,并删除仅与CMS有关的选项。尝试使用命令-XX:+UseConcMarkSweepGC开启CMS会收到以下警告:

Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; \
support was removed in <version>

VM将使用默认收集器继续执行。

JEP 364: macOS系统上的ZGC(实验)

将ZGC垃圾收集器移植到macOS。

动机

尽管我们希望需要ZGC可伸缩性的用户使用基于Linux的环境,但是在部署应用程序之前,开发人员通常会使用Mac进行本地开发和测试。 还有一些用户希望运行桌面应用程序,例如带有ZGC的IDE。

描述

ZGC的macOS实现由两部分组成:

  • 支持macOS上的多映射内存。 ZGC设计大量使用彩色指针,因此在macOS上我们需要一种将多个虚拟地址(在算法中包含不同颜色)映射到同一物理内存的方法。我们将为此使用mach microkernel mach_vm_remap API。堆的物理内存在单独的地址视图中维护,在概念上类似于文件描述符,但位于(主要是)连续的虚拟地址中。该内存被重新映射到内存的各种ZGC视图中,代表了算法的不同指针颜色。
  • ZGC支持不连续的内存保留。在Linux上,我们在初始化期间保留16TB的虚拟地址空间。我们假设没有共享库将映射到所需的地址空间。在默认的Linux配置上,这是一个安全的假设。但是在macOS上,ASLR机制会侵入我们的地址空间,因此ZGC必须允许堆保留不连续。假设VM实现使用单个连续的内存预留,则共享的VM代码也必须停止。如此一来,is_in_reserved(),reserved_region()和base()之类的GC API将从CollectedHeap中删除。

JEP 365: Windows系统上的ZGC(实验)

将ZGC垃圾收集器移植到Windows系统上。

描述

ZGC的大多数代码库都是平台无关的,不需要Windows特定的更改。现有的x64负载屏障支持与操作系统无关,也可以在Windows上使用。需要移植的特定于平台的代码与如何保留地址空间以及如何将物理内存映射到保留的地址空间有关。用于内存管理的Windows API与POSIX API不同,并且在某些方面不太灵活。

Windows实现的ZGC需要进行以下工作:

  • 支持多映射内存。 ZGC使用彩色指针需要支持堆多重映射,以便可以从进程地址空间中的多个不同位置访问同一物理内存。在Windows上,分页文件支持的内存为物理内存提供了一个标识(句柄),该标识与映射它的虚拟地址无关。使用此标识,ZGC可以将同一物理内存映射到多个位置。
  • 支持将分页文件支持的内存映射到保留的地址空间。 Windows内存管理API不如POSIX的mmap/munmap灵活,尤其是在将文件支持的内存映射到以前保留的地址空间区域中时。为此,ZGC将使用Windows概念的地址空间占位符。 Windows 10和Windows Server版本1803中引入了占位符概念。不会实现对Windows较早版本的ZGC支持。
  • 支持映射和取消映射堆的任意部分。 ZGC的堆布局与其动态调整堆页面大小(以及重新调整大小)相结合,需要支持映射和取消映射任意堆粒子。此要求与Windows地址空间占位符结合使用时,需要特别注意,因为占位符必须由程序显式拆分/合并,而不是由操作系统自动拆分/合并(如在Linux上)。
  • 支持提交和取消提交堆的任意部分。 ZGC可以在Java程序运行时动态地提交和取消提交物理内存。为了支持这些操作,物理内存将被划分为多个分页文件段并由其支持。每个分页文件段都对应一个ZGC堆粒度,并且可以独立于其他段进行提交和取消提交。

JEP 366: 弃用Parallel Scavenge

弃用Parallel Scavenge和Serial Old垃圾收集算法的组合。

动机

有一组GC算法的组合很少使用,但是维护起来却需要巨大的工作量:并行年轻代GC(ParallelScavenge)和串行老年代GC(SerialOld)的组合。用户必须使用-XX:+UseParallelGC -XX:-UseParallelOldGC来启用此组合。

这种组合是畸形的,因为它将并行的年轻代GC算法和串行的老年代GC算法组合在一起使用。我们认为这种组合仅在年轻代很多、老年代很少时才有效果。在这种情况下,由于老年代的体积较小,因此完整的收集暂停时间是可以接受的。但是在生产环境中,这种方式是非常冒险的:年轻代的对象容易导致OutOfMemoryException。此组合的唯一优势是总内存使用量略低。我们认为,这种较小的内存占用优势(最多是Java堆大小的约3%)不足以超过维护此GC组合的成本。

描述

除了弃用选项组合-XX:+UseParallelGC -XX:-UseParallelOldGC外,我们还将弃用选项-XX:UseParallelOldGC,因为它唯一的用途是取消选择并行的旧版GC,从而启用串行旧版GC。

因此,任何对UseParallelOldGC选项的明确使用都会显示弃用警告。

JEP 367: 移除Pack200工具和API

删除java.util.jar软件包中的pack200和unpack200工具以及Pack200 API。这些工具和API在Java SE 11中已经被注明为不推荐,并明确打算在将来的版本中删除它们。

动机

Pack200是JSR 200在Java SE 5.0中引入的一种JAR文件压缩方案。其目标是“减少Java应用程序打包,传输和交付的磁盘和带宽需求”。开发人员使用一对工具pack200和unpack200压缩和解压缩其JAR文件。在java.util.jar包中提供了一个API。

删除Pack200的三个原因:

  • 从历史上看,通过56k调制解调器缓慢下载JDK阻碍了Java的采用。 JDK功能的不断增长导致下载量膨胀,进一步阻碍了采用。使用Pack200压缩JDK是缓解此问题的一种方法。但是,时间已经过去了:下载速度得到了提高,并且JDK 9为Java运行时(JEP 220)和用于构建运行时的模块(JMOD)引入了新的压缩方案。因此,JDK 9和更高版本不依赖Pack200。 JDK 8是在构建时用pack200压缩的最新版本,在安装时用unpack200压缩的最新版本。总之,Pack200的主要使用者(JDK本身)不再需要它。
  • 除了JDK,Pack200还可以压缩客户端应用程序,尤其是applet。某些部署技术(例如Oracle的浏览器插件)会自动解压缩applet JAR。但是,客户端应用程序的格局已经改变,并且大多数浏览器都放弃了对插件的支持。因此,Pack200的主要消费者类别(在浏览器中运行的小程序)不再是将Pack200包含在JDK中的驱动程序。
  • Pack200是一项复杂而精致的技术。它的文件格式与类文件格式和JAR文件格式紧密相关,二者均以JSR 200所无法预料的方式发展。(例如,JEP 309向类文件格式添加了一种新的常量池条目,并且JEP 238在JAR文件格式中添加了版本控制元数据。)JDK中的实现是在Java和本机代码之间划分的,这使得维护变得很困难。 java.util.jar.Pack200中的API不利于Java SE平台的模块化,从而导致在Java SE 9中删除了其四种方法。总的来说,维护Pack200的成本是巨大的,并且超过了其收益。包括在Java SE和JDK中。
描述

JDK最终针对的JDK功能发行版中将删除以前用@Deprecated(forRemoval = true)注解的java.base模块中的三种类型:

  • java.util.jar.Pack200
  • java.util.jar.Pack200.Packer
  • java.util.jar.Pack200.Unpacker

包含pack200和unpack200工具的jdk.pack模块先前已使用@Deprecated(forRemoval = true)进行了注解,并且还将在此JEP最终针对的JDK功能版本中将其删除。

jdk13

JEP 354 switch表达式(预览)

引入

扩展switch分支选择语句的写法。Switch表达式在经过JDK 12的预览之后,在JDK 13中可以继续使用。

设计初衷

Java的switch语句是一个变化较大的语法(可能是因为Java的switch语句一直不够强大、熟悉swift或者js语言的同学可与swift的switch语句对比一下,就会发现Java的switch相对较弱),因为Java的很多版本都在不断地改进switch语句:JDK 12扩展了switch语句,使其可以用作语句或者表达式,并且传统的和扩展的简化版switch都可以使用。

JDK 12对于switch的增强主要在于简化书写形式,提升功能点。 下面简单回顾一下switch的进化阶段:

  • 从Java 5+开始,Java的switch语句可使用枚举了。
  • 从Java 7+开始,Java的switch语句支持使用String类型的变量和表达式了。
  • 从Java 11+开始,Java的switch语句会自动对省略break导致的贯穿提示警告。
  • 但从JDK12开始,Java的switch语句有了很大程度的增强。
  • JDK 13的该JEP是从JEP 325]演变而来的。

以前的switch程序

代码如下:

java
public class Demo01{
    public static void main(String[] args){
        // 声明变量score,并为其赋值为'C'
        var score = 'C';
        // 执行switch分支语句
        switch (score) {
            case 'A':
                System.out.println("优秀");
                break;
            case 'B':
                System.out.println("良好");
                break;
            case 'C':
                System.out.println("中");
                break;
            case 'D':
                System.out.println("及格");
                break;
            case 'E':
                System.out.println("不及格");
                break;
            default:
                System.out.println("数据非法!");
        }
    }
}

这是经典的Java 11以前的switch写法 ,这里不能忘记写break,否则switch就会贯穿、导致程序出现错误(JDK 11会提示警告)。

JDK 13不需要break了

在JDK 12之前如果switch忘记写break将导致贯穿,在JDK 12对switch的这一贯穿性做了改进。你只要将case后面的冒号(:)改成箭头,那么你即使不写break也不会贯穿了,因此上面程序可改写如下形式:

java
public class Demo02{
    public static void main(String[] args){
        // 声明变量score,并为其赋值为'C'
        var score = 'C';
        // 执行switch分支语句
        switch (score){
            case 'A' -> System.out.println("优秀");
            case 'B' -> System.out.println("良好");
            case 'C' -> System.out.println("中");
            case 'D' -> System.out.println("及格");
            case 'E' -> System.out.println("不及格");
            default -> System.out.println("成绩数据非法!");
        }
    }
}

上面代码简洁很多了。

JDK 13的switch表达式

JDK 12之后的switch甚至可作为表达式了——不再是单独的语句。例如如下程序。

java
public class Demo03 {
    public static void main(String[] args) {
		// 声明变量score,并为其赋值为'C'
        var score = 'C';
		// 执行switch分支语句
        String s = switch (score)
                {
                    case 'A' -> "优秀";
                    case 'B' -> "良好";
                    case 'C' -> "中";
                    case 'D' -> "及格";
                    case 'F' -> "不及格";
                    default -> "成绩输入错误";
                };
        System.out.println(s);
    }
}

上面程序直接将switch表达式的值赋值给s变量,这样switch不再是一个语句,而是一个表达式.

JDK 13中switch的多值匹配

当你把switch中的case后的冒号改为箭头之后,此时switch就不会贯穿了,但在某些情况下,程序本来就希望贯穿比如我就希望两个case共用一个执行体!JDK 12之后的switch中的case也支持多值匹配,这样程序就变得更加简洁了。例如如下程序。

java
public class Demo04
{
    public static void main(String[] args)
    {
// 声明变量score,并为其赋值为'C'
        var score = 'B';
// 执行switch分支语句
        String s = switch (score)
                {
                    case 'A', 'B' -> "上等";
                    case 'C' -> "中等";
                    case 'D', 'E' -> "下等";
                    default -> "成绩数据输入非法!";
                };
        System.out.println(s);
    }
}

JDK 13的Yielding a value

当使用箭头标签时,箭头标签右边可以是表达式、throw语句或是代码块。如果是代码块,需要使用yield语句来返回值。下面代码中的print方法中的default语句的右边是一个代码块。在代码块中使用yield来返回值。,JDK 13引入了一个新的yield语句来产生一个值,该值成为封闭的switch表达式的值。

java
public void print(int days) {
  // 声明变量score,并为其赋值为'C'
  var score = 'B';
  String result = switch (score) {
      case 'A', 'B' -> "上等";
      case 'C' -> "中等";
      case 'D', 'E' -> "下等";
      default -> {
          if (score > 100) {
            yield "数据不能超过100";
          } else {
            yield score + "此分数低于0分";
          }
      }
  };
  System.out.println(result);
}

​ 在switch表达式中不能使用breakswitch表达式的每个标签都必须产生一个值,或者抛出异常。switch表达式必须穷尽所有可能的值。这意味着通常需要一个default语句。一个例外是枚举类型。如果穷尽了枚举类型的所有可能值,则不需要使用default。在这种情况下,编译器会自动生成一个default语句。这是因为枚举类型中的枚举值可能发生变化。比如,枚举类型Color 中原来只有3个值:REDGREENBLUE。使用该枚举类型的switch表达式穷尽了3种情况并完成编译。之后Color中增加了一个新的值YELLOW,当用这个新的值调用之前的代码时,由于不能匹配已有的值,编译器产生的default会被调用,告知枚举类型发生改变

小结

从以上案例可以看出JDK 12到JDK 13对switch的功能做了很大的改进,代码也十分的简化,目前来看switch依然是不支持区间匹配的,未来是否可以支持,我们拭目以待。

JEP 355 文本块升级(预览)

引入

在Java中,在字符串文字中嵌入HTML,XML,SQL或JSON片段"..."通常需要先进行转义和串联的大量编辑,然后才能编译包含该片段的代码。该代码段通常难以阅读且难以维护,因此,如果具有一种语言学机制,可以比多行文字更直观地表示字符串,而且可以跨越多行,而且不会出现转义的视觉混乱,那么这将提高广泛Java类程序的可读性和可写性。从JDK 13到JDK 13开始文本块新特性的提出,提高了Java程序书写大段字符串文本的可读性和方便性。

HTML示例

使用“一维”字符串文字*

java
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

使用“二维”文本块

java
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;
    
System.out.println("""
    Hello,
    itheima
    text blocks!
    """);

文本块是Java语言的新语法,可以用来表示任何字符串,具有更高的表达能力和更少的复杂度。文本块的开头定界符是由三个双引号 """ 开始,从新的一行开始字符串的内容。这里的新起的这行不属于字符串,只表示内容开始,是语法的一部分。以 """ 结束。 """ 可以紧跟字符串内容,也可以另起一行。另起一行时,字符串内容最后会留有一新行。

java
"""
line 1
line 2
line 3
"""

等效于字符串文字:

java
"line 1\nline 2\nline 3\n"

或字符串文字的串联:

java
"line 1\n" +
"line 2\n" +
"line 3\n"

如果在字符串的末尾不需要行终止符,则可以将结束定界符放在内容的最后一行。例如,文本块:

java
"""
line 1
line 2
line 3"""

等效于字符串文字:

java
"line 1\nline 2\nline 3"

文本块可以表示空字符串,尽管不建议这样做,因为它需要两行源代码:

java
String empty = """
""";

以下是一些格式错误的文本块的示例:

java
String a = """""";   // no line terminator after opening delimiter
String b = """ """;  // no line terminator after opening delimiter
String c = """
           ";        // no closing delimiter (text block continues to EOF)
String d = """
           abc \ def
           """;      // unescaped backslash (see below for escape processing)

HTML

使用原始字符串语法:

java
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

使用文本块文本块语法:

java
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

SQL

使用原始的字符串语法:

java
String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
               "WHERE `CITY` = 'INDIANAPOLIS'\n" +
               "ORDER BY `EMP_ID`, `LAST_NAME`;\n";

使用文本块语法:

java
String query = """
               SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
               WHERE `CITY` = 'INDIANAPOLIS'
               ORDER BY `EMP_ID`, `LAST_NAME`;
               """;

多语言示例

使用原始的字符串语法:

java
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
                         "    print('\"Hello, world\"');\n" +
                         "}\n" +
                         "\n" +
                         "hello();\n");

使用文本块语法:

java
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
                         function hello() {
                             print('"Hello, world"');
                         }
                         
                         hello();
                         """);

缩进

java编译器会自动删除不需要的缩进:

  • 每行结尾的空格都会删除
  • 每行开始的共有的空格会自动删除
  • 只保留相对缩进。
python
System.out.println("""
    Hello,
    itheima
    text blocks!
    """);
// 结果
// > Hello,
// > itheima
// > text blocks!
// >

System.out.println("""
    Hello,
      itheima
    text blocks!
    """);
// 结果
// > Hello,
// >   itheima
// > text blocks!
// >
  • 新行 """ 结束时,将 """ 向左调整,则可以给所有行前加相应数量的空格。将 """ 向右调整,没有作用。
python
System.out.println("""
        Hello,
        multiline
        text blocks!
    """);
// 结果
// >     Hello,
// >     multiline
// >     text blocks!

JEP 350 动态类数据共享归档

作用:允许在Java应用程序执行结束时动态归档类。归档的类将包括默认基层CDS归档中不存在的所有已加载应用程序类和库类。

CDS,是java 12的特性了,可以让不同 Java 进程之间共享一份类元数据,减少内存占用,它还能加快应用的启动速度。而JDK13的这个特性支持在Java application执行之后进行动态archive。存档类将包括默认的基础层CDS存档中不存在的所有已加载的应用程序和库类。也就是说,在Java 13中再使用AppCDS的时候,就不再需要这么复杂了。该提案处于目标阶段,旨在提高AppCDS的可用性,并消除用户进行试运行以创建每个应用程序的类列表的需要。

目标

JDK13这次对CDS增强的主要目的

  • 改善AppCDS的可用性,减少用户每次都要创建一个类列表的需要
  • 通过开启, -Xshare:dump 选项来开启静态归档,使用类列表仍然行得通。会包含内置的类加载信息和用户定义的类加载信息

意义

在JDK13中做的增强,可以只开启命令行选项完成上面过程,在程序运行的时候,动态评估那些类需要归档,同时支持内置的类加载器和用户定义的类加载器。

在第一次程序执行完成之后,会自动的将类进行归档,后续在启动项目的时候也无需指定要使用哪些归档,整个过程看起来更加的透明。

JEP 351 ZGC 增强: ZGC 释放未使用内存

目标

作用:将未使用的堆内存还给系统(即未提交的内存空间)

详解

ZGC 是 Java 11 中引入的最为瞩目的垃圾回收特性,是一种可伸缩、低延迟的垃圾收集器,不过在 Java 11 中是实验性的引入,主要用来改善 GC 停顿时间,并支持几百 MB 至几个 TB 级别大小的堆,并且应用吞吐能力下降不会超过 15%,目前只支持 Linux/x64 位平台的这样一种新型垃圾收集器。

通过在实际中的使用,发现 ZGC 收集器中并没有像 Hotspot 中的 G1 和 Shenandoah 垃圾收集器一样,能够主动将未使用的内存释放给操作系统的功能。对于大多数应用程序来说,CPU 和内存都属于有限的紧缺资源,特别是现在使用的云上或者虚拟化环境中。如果应用程序中的内存长期处于空闲状态,并且还不能释放给操作系统,这样会导致其他需要内存的应用无法分配到需要的内存,而这边应用分配的内存还处于空闲状态,处于"忙的太忙,闲的太闲"的非公平状态,并且也容易导致基于虚拟化的环境中,因为这些实际并未使用的资源而多付费的情况。由此可见,将未使用内存释放给系统主内存是一项非常有用且亟需的功能。

Java 13 中对 ZGC 的改进,主要体现在下面几点:

  • 释放未使用内存给操作系统
  • 支持最大堆大小为 16TB

Java 13 中,ZGC 内存释放功能,默认情况下是开启的,不过可以使用参数:-XX:-ZUncommit 显式关闭。

JEP 353 重新实现旧版Socket API

引入

现在已有的 java.net.Socket 和 java.net.ServerSocket 以及它们的实现类,都可以回溯到 JDK 1.0 时代了。原始socket的维护和调试都很痛苦。实现类还使用了线程栈作为 I/O 的缓冲,导致在某些情况下还需要增加线程栈的大小。该实现还存在几个并发问题,需要彻底解决。在未来的网络世界,要快速响应,不能阻塞本地方法线程,当前的实现不适合使用了。

详解

新的实现类

在 Java 13 之前,通过使用 PlainSocketImpl 作为 SocketImpl 的具体实现。

Java 13 中的新底层实现,引入 NioSocketImpl 的实现用以替换 SocketImplPlainSocketImpl 实现,此实现与 NIO(新 I/O)实现共享相同的内部基础结构,并且与现有的缓冲区高速缓存机制集成在一起,因此不需要使用线程堆栈。除了这些更改之外,还有其他一些更便利的更改,如使用 java.lang.ref.Cleaner 机制来关闭套接字(如果 SocketImpl 实现在尚未关闭的套接字上被进行了垃圾收集),以及在轮询时套接字处于非阻塞模式时处理超时操作等方面。

全新实现的 NioSocketImpl 来替换JDK1.0的PlainSocketImpl。

  • 它便于维护和调试,与 NewI/O (NIO) 使用相同的 JDK 内部结构,因此不需要使用系统本地代码。
  • 它与现有的缓冲区缓存机制集成在一起,这样就不需要为 I/O 使用线程栈。
  • 它使用 java.util.concurrent 锁,而不是 synchronized 同步方法,增强了并发能力。
  • 新的实现是Java 13中的默认实现,但是旧的实现还没有删除,可以通过设置系统属性 jdk.net.usePlainSocketImpl来切换到旧版本。

代码说明

运行一个实例化Socket和ServerSocket的类将显示这个调试输出。这是默认的(新的)。

java
Module java.base
Package java.net
Class SocketImpl
public abstract class SocketImpl implements SocketOptions {
  private static final boolean USE_PLAINSOCKETIMPL = usePlainSocketImpl();
  private static boolean usePlainSocketImpl() {
    PrivilegedAction<String> pa = () ->
NetProperties.get("jdk.net.usePlainSocketImpl");
    String s = AccessController.doPrivileged(pa);
    return (s != null) && !s.equalsIgnoreCase("false");
 }
  /**

Creates an instance of platform's SocketImpl
*/
  @SuppressWarnings("unchecked")static <S extends SocketImpl & PlatformSocketImpl> S
createPlatformSocketImpl(boolean server) {
    if (USE_PLAINSOCKETIMPL) {
      return (S) new PlainSocketImpl(server);
   } else {
      return (S) new NioSocketImpl(server);
   }
 }
}

SocketImpl的USE_PLAINSOCKETIMPL取决于usePlainSocketImpl方法,而它会从NetProperties读取 dk.net.usePlainSocketImpl配置,如果不为null且不为false,则usePlainSocketImpl方法返回true; createPlatformSocketImpl会根据USE_PLAINSOCKETIMPL来创建PlainSocketImpl或者NioSocketImpl。

通过这些更改,Java Socket API 将更易于维护,更好地维护将使套接字代码的可靠性得到改善。同时 NIO 实现也可以在基础层面完成,从而保持 Socket 和 ServerSocket 类层面上的不变。

其他特性

上面列出的是大方面的特性,除此之外还有一些api的更新及废弃,主要见 JDK 13 Release Notes,这里举几个例 子。 https://jdk.java.net/13/release-notes

增加项

添加FileSystems.newFileSystem(Path, Map<String, ?>) Method 新的java.nio.ByteBuffer Bulk get/put Methods Transfer Bytes Without Regard to Buffer Position 支持Unicode 12.1 添加-XX:SoftMaxHeapSize Flag,目前仅仅对ZGC起作用 ZGC的最大heap大小增大到16TB

移除项

移除awt.toolkit System Property 移除Runtime Trace Methods 移除-XX:+AggressiveOpts 移除Two Comodo Root CA Certificates、Two DocuSign Root CA Certificates 移除内部的com.sun.net.ssl包

废弃项

废弃-Xverify:none及-noverify 废弃rmic Tool并准备移除 废弃javax.security.cert并准备移除

已知问题

不再支持Windows 2019 Core Server 使用ZIP File System (zipfs) Provider来更新包含Uncompressed Entries的ZIP或JAR可能造成文件损坏