当前位置:网站首页 > Java基础 > 正文

java14基础知识




highlight: xcode

theme: vuepress

概述

Oracle 在 2020 年 3 月 17 日宣布 Java 14 全面上市,Java 14 通过每六个月发布一次新功能,为企业和开发人员社区提供增强功能,继续了 Oracle 加快创新的承诺。最新的 Java 开发工具包提供了新功能,其中包括两项备受期待的新预览功能,实例匹配的匹配模式(JEP 305)和记录(JEP 359),以及文本块的第二个预览(JEP 368),此外,最新的 Java 版本增加了对 switch 表达式的语言支持。公开了用于持续监控 JDK Flight Recorder 数据的新API,将低延迟的 Z 垃圾收集器的可用性扩招到了 MacOS 和 Windows,并在孵化器模块中添加了包装完备的 Java 应用程序和新的外部内存访问 API,以安全高效的访问 Java 对外部的内存。

我们可以在 OpenJDK 官网中观察到 Java 14 发布的详细官方计划和具体新特性详情,地址如下:

https://openjdk.java.net/projects/jdk/14/

Java 14 一共发行了 16 个 JEP(JDK Enhancement Proposals,JDK 增强提案)。

语法层次变化

instanceof

以往我们使用 运算符都是先判断,然后在进行强转,例如我们查看 的 方法源码:

需要先判断类型,然后强转。还要声明一个本地变量,语法比较麻烦。比较理想的状态是,在执行类型检测的时候同时执行类型转换。

JEP305 新增了使 运算符具有匹配的能力。模式匹配能够是程序的通用逻辑更加简洁,代码更加简单,同时在做类型判断和类型转换的时候也更加安全。详情如下:

Java 14 提供了新的解决方案:新的 模式匹配,新的模式匹配语法是:在 的类型之后添加了变量。如果对 的类型检查通过, 会被转换成后面的变量表示的数据类型。数据类型的声明仅仅书写一次即可。

上述语法的判断逻辑时,如果 是 类型,则会转换为后面的 ,如果不是,则执行 。注意,此时的 仅仅是 语句块里的局部变量,在 语句块中不可用。

但是如果 语句中使用了 这种取反运算,那么逻辑上就是相反的,这个时候 才是相当于成功转换了,所以在 中可以使用 , 中不可以使用 。

上述语句块中,如果 中的判断逻辑比较复杂,是可以在后续的其他条件中使用 变量进行判断的。但是注意这里的运算符是短路与运算,就是要保证后面在使用 时,已经完成了转换,如果使用短路或运算,无法保证 是可以成功转换的,是不允许的。如下面的代码就是错的:

总之: 语句块中的小括号内,要保证成功的进行了转换才可以在 语句库中使用转换的对象,否则不可以。

switch表达式(转正)

Java 14 的 JEP 361 switch 表达式(标准)是独立的,不依赖于 JEP 325 和 JEP 354,也就是说这里开始,之前学习的 switch 语句的语法成为一个正式的标准。

text block

从 Java 13 引入的文本块语法在 Java 14 中做了下列改进:

  • 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化 Java 程序。
  • 增强 Java 程序中用字符串表示其他语言代码的可读性。
  • 解析新的转义序列。

记录类型Records

通过 增强 Java 编程语言。 提供了一种紧凑的语法来声明类,这些类是浅层不可变数据的透明持有者。

传统的类如下:

是 Java 的一种新的类型。同枚举一样, 也是对类的一种限制, 放弃了类通常享有的特性:将 API 和表示解耦。但是作为回报, 使数据类型变得非常简洁,一般可以帮助我们定义一些简单的用于传递数据的实体类。

一个 具有名称和状态描述,状态描述声明了 的组成部分。

因为 是数据的简单透明持有者,所以 会自动获取很多的标准成员。

  • 状态声明中的每个成员,都是一个 的字段,属性设置值则不可修改。
  • 状态声明中的每个组件的公共读取访问方法,该方法和组件具有相同的名字, 方法和属性名一致。
  • 一个公共的构造函数,其签名与状态声明相同,构造方法和签名合二为一。
  • 和 的实现。
  • 的实现。
  • 提供的默认是一个全参的构造器。

测试代码如下:

```java public class Test { public static void main(String[] args) { Person p1 = new Person(1, "张三", 10); Person p2 = new Person(1, "张三", 10); System.out.println(p1.name()); System.out.println(p1); System.out.println(p1.hashCode()); System.out.println(p2.hashCode()); System.out.println(p1.equals(p2)); } }

record Person(Integer pid, String pname, Integer page) {};

// 张三 // Person[pid=1, pname=张三, page=10] // // // true ```

类型是隐含的 类,并且不是抽象类。 不能拓展任何类,不能被继承,声明的任何其他字段都必须是静态()的。 的 API 仅仅能由其状态描述定义(通过属性定义)。

也可以显示声明从状态描述自动派生的任何成员,可以在没有正式参数列表的情况下声明构造函数。并且在正常的构造方法主体正常完成时调用隐式初始化,这样就可以在显示构造函数中仅执行其参数的验证逻辑,并且省略字段的初始化。

测试代码如下:

```java public class Test2 { public static void main(String[] args) { Person p1 = new Person(1, "张三", 10); Person p2 = new Person(1, java14基础知识 "张三", 10); System.out.println(p1.name()); System.out.println(p1); System.out.println(p1.hashCode()); System.out.println(p2.hashCode()); System.out.println(p1.equals(p2)); } }

record Person(Integer pid, String name, Integer page) {

 

}; ```

关于GC

G1的NUMA的内存分配优化

NUMA 就是非统一内存访问架构(英语:non-uniform memory access,简称 NUMA),是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。在 NUMA 下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。如下图所示,Node0 中的 CPU 如果访问Node0 中的内存,那就是访问本地内存,如果它访问了 Node1 中的内存,那就是远程访问,性能较差:

image.png

非统一内存访问架构的特点是:

被共享的内存物理上是分布式的,所有这些内存的集合就是全局地址空间。所以处理器访问这些内存的时间是不一样的,显然访问本地内存的速度要比访问全局共享内存或远程访问外地内存要快些。另外,NUMA 中内存可能是分层的:本地内存,群内共享内存,全局共享内存。

JEP 345 希望通过实现 NUMA-aware 的内存分配,改进 G1 在大型机上的性能。

现代的 multi-socket 服务器越来越多都有 NUMA,意思是,内存到每个 socket 的距离是不相等的,内存到不同的socket 之间的访问是有性能差异的,这个距离越长,延迟就会越大,性能就会越差!(https://openjdk.java.net/jeps/345)

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

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

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

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

JEP 345 专门用于实现 G1 垃圾收集器的 NUMA 支持,仅用于内存管理(内存分配),并且仅在 Linux 下。对于NUMA 体系结构的这种支持是否也适用于其他垃圾回收器或其他部分(例如任务队列窃取),尚不清楚。

弃用Serial+CMS,ParNew+Serial Old

由于维护和兼容性测试的成本,在 Java 8 时将 Serial+CMS、ParNew+Serial Old 这两个组合声明为废弃(JEP173),并在 Java 9 中完全取消了这些组合的支持(JEP214)。

image.png

ParallelScavenge + SerialOld GC 的 GC 组合要被标记为 Deprecate 了。

这个 GC 组合需要大量的代码维护工作,并且这个 GC 组合很少被使用。因为它的使用场景应该是一个很大的 Young 区和一个很小的 Old 区,这样的话 Old 区用 SerialOld GC 去收集停顿时间才可以勉强被接受。

废弃了 Parallel Young Generation GC 与 Serial Old GC 组合( 与 配合开启),现在使用 或者使用 会出现如下警告:

image.png

删除CMS

自从 G1 出现后,CMS 在 Java 9 中就被标记为 Deprecate 了。

CMS 弊端:

  • 会产生内存碎片,导致并发清除后,用户线程可用空间不足(标记清除算法产生,需要整理算法解决)。
  • 既然强调了并发(Concurrent)CMS 收集器对于 CPU 资源非常敏感,导致吞吐量降低。
  • CMS 收集器无法处理浮动垃圾(用户线程和垃圾回收线程并发执行,回收时用户线程产生新的垃圾)。

当 CMS 停止工作时,会把 Serial Old GC 作为备选方案,而它是 JVM 中性能最差的垃圾收集方式,停顿几秒甚至十秒都有可能。

移除了 CMS 垃圾收集器,如果继续在 Java 14 中使用 不会报错。仅仅给出一个warning 警告:

ZGC

Java 14 之前,ZGC 仅仅支持 Linux。

基于一些开发部署和测试的需要,ZGC 在 Java 14 中支持在 MacOS 和 Windows,因此许多桌面级应用可以从 ZGC 中受益,目前还是一个实验性版本,要想在 MacOS 和 Windows 上使用。

ZGC 与 Shenandoah 目标非常相似,都是在尽量减少吞吐量的情况下,实现对任意堆大小(TB 级)都可以把垃圾收集器停顿时间限制在 10ms 以内的低延迟时间。

ZGC 收集器是一款基于 Region 内存布局的。暂时不设分代的,使用了读屏障,染色指针和内存多重映射等技术来实现并发的标记压缩算法,以低延迟为首要目标的一款垃圾收集器。现在想在 MacOS 和 Windows 上使用 ZGC,方式如下:

ZGC 目前还处于一个实验状态,但是性能非常亮眼,未来将在服务端,大内存、低延迟应用上作为首选的垃圾收集器。

其他

友好的空指针异常提示

是 Java 开发中经常遇见的问题。在 Java 14 之前的版本中,空指针异常的提示信息就是简单的 ,并不会告诉我们更加有用的信息,只是根据异常产生的日志来进行查找和处理。对于很长的引用来说,很难定位到具体是哪个对象为 null。

```java public class Test2 { public static void main(String[] args) { Person p =new Person(); p.cat.eat(); } }

class Person { public Cat cat; }

class Cat { public void eat() {

 

} ```

上面的代码在调用 方法时就会出现空指针异常:

image.png

这种提示其实并不是很详细,我们可以在运行代码的时候,加上一段配置,用以展示比较友好的控制成提示信息:

输出的信息如下所示:

image.png

Java打包工具

该特征旨在创建一个用于打包独立 Java 应用程序的工具。Java 应用的打包和分发一直都是个老大的难题。用户希望Java 引用的安装和运行方式和其他应用有相似的体验。比如,在 Windows 上只需要双击文件就可以运行。Java 平台本身没有提供实用的工具解决这个问题。通常都依赖第三方的工具完成,这个 JEP 的目标就是创建一个简单的 Java 打包工具 jpackage。相对于第三方工具,jpackage 只适用于比较简单的场景,不过对很多应用来说已经足够好了。

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

  1. Linux:deb 或者 rpm
  2. MaxOS:pkg 和 dmg
  3. Windows:msi 和 exe

如果有一个包含 jar 文件的应用程序,所有的应用程序都位于一个名为 lib 的目录中,并且 lib/main.jar 包含主类,可以通过如下命令打包:

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

软件包的名称将为没有 app,尽管软件包文件本身的名称将更长,并以软件包类型皆为该软件包将包括该应用程序的启动器,也称为 myapp。要启动应用程序,启动程序将会从输入目录复制的每个 jar 文件放在 jvm 的类路径上。

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

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

如果 myapp 模块未标识主类,则必须再次明确:

JFR事件流

Java Flight Recorder(JFR)是 JVM 的诊断和性能分析工具。

Java 14 之前只能做离线的分析,现在可以做实时的持续监视。

它可以收集有关 JVM 以及在其上运行的 Java 应用程序的数据。JFR 是集成到 JVM 中的,所以 JFR 对 JVM 的性能影响非常小,我们可以放心的使用它。

一般来说,在使用默认配置的时候,性能影响要小于 1%。

JFR 的历史很久远了。早在 Oracle 2008 年收购 BEA 的时候就有了。JFR 一般和 JMC(Java Mission Control)协同工作。

JFR 是一个基于事件的低开销的分析引擎,具有高性能的后端,可以以二进制格式编写事件,而 JMC 是一个 GUI 工具,用于检查 JFR 创建的数据文件。

这些工具最早是在 BEA 的 JRockit JVM 中出现的,最后被移植到了 Oracle JDK。最开始 JFR 是商用版本,但是在 Java 11 的时候,JFR 和 JMC 完全开源了,这意味着我们在非商用的情况下也可以使用了。

而在今天的 Java 14 中,引入了一个新的 JFR 特性叫做 JFR Event Streaming,我们将在本文中简要介绍。

先介绍一下 JFR 和 JMC。

JFR:

JFR 是 JVM 的调优工具,通过不停的收集 JVM 和 Java 应用程序中的各种事件,从而为后续的 JMC 分析提供数据。

Event 是由三部分组成的:时间戳,事件名和数据。同时 JFR 也会处理三种类型的 Event:持续一段时间的 Event,立刻触发的 Event 和 抽样的 Event。

为了保证性能的最新影响,在使用 JFR 的时候,请选择你需要的事件类型。

JFR 从 JVM 中搜集到 Event 之后,会将其写入一个小的 thread-local 缓存中,然后刷新到一个全局的内存缓存中,最后将缓存中的数据写到磁盘中去。

或者你可以配置 JFR 不写到磁盘中去,但是这样缓存中只会保存部分 events 的信息。这也是有 Java 14 JEP 349的原因。

开启 JFR 有很多种方式,这里我们关注下面两种:

  1. 添加命令行参数:

启动命令行参数的格式如上所述。JFR 可以获取超过一百种不同类型的元数据。如果要我们一个个来指定这些元数据,将会是一个非常大的功能。所以 JDK 已经为我们提供了两个默认的 profile: default.jfc and profile.jfc。

其中 default.jfc 是默认的记录等级,对 JVM 性能影响不大,适合普通的,大部分应用程序。而 profile.jfc 包含了更多的细节,对性能影响会更多一些。

如果你不想使用默认的两个 jfc 文件,也可以按照你自己的需要来创建。下面看一个更加完整的命令行参数:

上面的命令会创建一个最大 age 是 5h 的 profile 信息文件。

  1. 使用 jcmd:

命令行添加参数还是太麻烦了,如果我们想动态添加 JFR,则可以使用 jcmd 命令:

上面的命令在一个运行中的 JVM 中启动了 JFR,并将统计结果 dump 到了文件中。上面的 custProfile.jfr 是一个二进制文件,为了对其进行分析,我们需要和 JFR 配套的工具 JMC。

JMC:

JDK Mission Control 是一个用于对 Java 应用程序进行管理、监视、概要分析和故障排除的工具套件。在 Java 14 中,JMC 是独立于 JDK 单独发行的。我们可以下载之后进行安装。我们先启动一个程序,用于做 JFR 的测试。

```java @Slf4j public class ThreadTest {

 

} ```

很简单的一个程序,启动了 10 个线程,我们启动这个程序。

JMC 好用是好用,但是要一个一个的去监听 JFR 文件会很繁琐。接下来我们来介绍一下怎么采用写代码的方式来监听JFR 事件。

如果我们想通过程序来获取 "Class Loading Statistics" 的信息,可以这样做。

上图的右侧是具体的信息,我们可以看到主要包含三个字段:开始时间,Loaded Class Count 和 Unloaded Class Count。

我们的思路就是使用 jdk.jfr.consumer.RecordingFile 去读取生成的 JFR 文件,然后对文件中的数据进行解析。

相应代码如下:

```java @Slf4j public class JFREvent {

 

} ```

注意,在 方法中,我们将从文件中读取的 Event 转换成了 map 对象。

在构建 map 时,我们先判断 Event 的名字是不是我们所需要的 jdk.ClassLoadingStatistics,然后将 Event 中其他的字段进行转换,最后输出。

运行结果:

上面的 JFR 事件中,我们需要去读取 JFR 文件,进行分析。但是文件是死的,人是活的,每次分析都需要先生成 JFR 文件简直是太复杂了。是个程序员都不能容忍。

在 JFR 事件流中,我们可以监听 Event 的变化,从而在程序中进行相应的处理。这样不需要生成JFR文件也可以监听事件变化。

看看上面的例子。我们通过 Configuration.getConfiguration("default") 获取到了默认的 default 配置。然后通过构建了 default 的 RecordingStream。通过 onEvent 方法,我们对相应的 Event 进行处理。

外部存储器API

通过一个 API,以允许 Java 程序安全有效的访问 Java 堆之外的外部存储(堆以外的外部存储空间)。

目的:JEP 370旨在实现一种提供“通用性”,“安全性”和“确定性”的“外部存储器API”。JEP 还指出,此外部内存 API 旨在替代当前使用的方法(java.nio.ByteBuffer 和 sun.misc.Unsafe)。

许多 Java 的库都能访问外部存储,例如 Ignite、MapDB、Memcached 以及 Netty 的 ByteBuffer API,这样可以:

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

但是 Java API 本身没有提供一个令人满意的访问外部内存的解决方案。当 Java 程序需要访问堆内存之外的外部存储时,通常有两种方式:

  • java.nio.ByteBuffer:ByteBuffer 允许使用 方法在堆内存之外分配内存空间。
  • sum.misc.Unsafe:Unsafe 中的方法可以直接对内存地址进行操作。

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

JEP 370 的“描述”部分引入了安全高效的 API 来访问外部外部内存地址,目前该 API 还是属于孵化阶段,相关 API 在 jdk.incubator.foreign 模块的 jdk.incubator.foreign 包中,三个 API 分别是:, 和 。 用于对具有给定空间和时间范围的连续内存区域进行建模。可以将 视为段内的偏移量。最后, 是内存段内容的程序化描述。

非易失性映射字节缓存区

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

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

NVM为引用程序程序员提供了在程序运行过程中创建和更新程序转台的机会,而减少了输出到持久性介质或者从持久性介质输入是的成本. 对于事务程序特别重要,在事务程序中,需要定期保持不确定状态以启用崩溃恢复.

现有的 C 库(例如 Intel 的 libpmen),为 C 程序员提供了对集成 NVM 的高效访问,它们还以此基础来支持对各种持久性数据类型的简单管理。当前,由于频繁需要进行系统调用或者 JNI 来调用原始操作,从而确保内存更改是持久的,因此即使禁用 Java 的基础类库也很昂贵。同样的问题限制了高级库的使用。并且,由于 C 中提供的持久数据类型分配在无法从 Java 直接访问的内存中这一事实而加剧了这一问题。

  • 上一篇: java基础题测试
  • 下一篇: java基础篇第五章
  • 版权声明


    相关文章:

  • java基础题测试2024-10-28 12:26:05
  • java包装基础知识2024-10-28 12:26:05
  • java围棋从基础到2024-10-28 12:26:05
  • java开发需要懂的基础知识2024-10-28 12:26:05
  • java前端开发基础2024-10-28 12:26:05
  • java基础篇第五章2024-10-28 12:26:05
  • java基础语法网课2024-10-28 12:26:05
  • 精通java基础能找到工作嘛2024-10-28 12:26:05
  • 有java基础怎么赚钱2024-10-28 12:26:05
  • java怎么算有基础2024-10-28 12:26:05