# 发现 Java11 在上一章中,我们探讨了新实现的 Java 平台基于时间的版本控制系统。我们还从高层次了解了 Java9、10 和 11 中引入的更改,这些更改分别称为 9、18.3 和 18.9 版本。Java9 最重要的变化是引入了基于 Jigsaw 项目的模块化,包括关注 Javashell、控制外部进程、垃圾收集、JHM 等的其他变化。介绍了 Java10 的主要特性,包括局部变量类型推断、JDK 合并、垃圾收集、应用程序**类数据共享**(**CDS**)、根证书等。Java11 中引入的更改包括动态类文件常量、垃圾收集、Lambda 的局部变量类型推断等等。 在本章中,我们将介绍几个引入 Java 平台的内部更改,包括来自 Java9、10 和 11 的更改。Java9 是 Java 平台的主要版本;Java10 和 11 是定时版本。总的来说,这些版本包含了大量的内部更改,为 Java 开发人员提供了一系列新的可能性,有些源于开发人员的请求,有些源于 Oracle 的增强 在本章中,我们将回顾 29 个最重要的变化。每个变更都与一个 **JDK 增强方案**(**JEP**)相关。JEP 索引并存放在[这个页面](http://openjdk.java.net/jeps/0)。您可以访问此链接以获取有关每个 JEP 的更多信息。 JEP 计划是 Oracle 支持开源、开放创新和开放标准的一部分。虽然可以找到其他开源 Java 项目,但 OpenJDK 是 Oracle 唯一支持的项目。 在本章中,我们将介绍以下内容: * 改进的争用锁 * 分段码缓存 * 智能 Java 编译,第二阶段【JEP199】 * 解决 Lint 和 Doclint 警告【JEP212】 * Javac 的分层属性【JEP215】 * 注释管道 2.0【JEP217】 * 新版本字符串方案 * 自动生成运行时编译器测试【JEP233】 * 测试 Javac【JEP235】生成的类文件属性 * 在 CD 档案中存储内部字符串【JEP250】 * 为模块化准备 JavaFXUI 控件和 CSS API【JEP253】 * 紧弦 * 将选定的 Xerces 2.11.0 更新合并到 JAXP【JEP255】 * 将 JavaFX/Media 更新为 GStreamer 的更新版本【JEP257】 * HarfBuzz 字体布局引擎 * Windows 和 Linux 上的 HiDPI 图形【JEP263】 * 马林图形渲染器 * Unicode 8.0.0[JEP267 和 JEP314] * 临界截面预留堆放区【JEP270】 * 语言定义对象模型的动态链接 * G1 中大型物体的附加试验【JEP278】 * 改进测试故障排除 * 优化字符串串联 * 热点 C++ 单元测试框架【JEP281】 * 在 Linux 上启用 GTK3【JEP283】 * 新热点构建系统 * 将 JDF 林整合到单个存储库中【JEP296】 # 技术要求 本章及后续章节以 Java11 为特色,Java 平台的**标准版**(**SE**)可从 [Oracle 官方下载网站的链接](http://www.oracle.com/technetwork/java/javase/downloads/index.html)下载。 一个**集成开发环境**(**IDE**)软件包就足够了。来自 JetBrains 的 IntelliJ IDEA 用于与本章和后续章节相关的所有编码。IntelliJ IDEA 的社区版可从[网站](https://www.jetbrains.com/idea/features/)下载。 本章的源代码可以在 [GitHub 的 URL](https://github.com/PacktPublishing/Mastering-Java-11-Second-Edition)上找到。 # 改进的争用锁定 JVM 将堆空间用于类和对象。每当我们创建一个对象时,JVM 就会在堆上分配内存。这有助于促进 Java 的垃圾收集,垃圾收集释放以前用来保存不再有内存引用的对象的内存。Java 栈内存有点不同,通常比堆内存小得多 JVM 在管理由多个线程共享的数据区域方面做得很好。它将监视器与每个对象和类相关联;这些监视器具有在任何时候由单个线程控制的锁。这些由 JVM 控制的锁本质上是给控制线程对象的监视器?当一个线程在一个队列中等待一个当前被锁定的对象时,它就被认为是在争夺这个锁。下图显示了此争用的高级视图: ![](img/052c1197-4bb1-4760-ba9a-ad858eec2af6.png) 正如您在前面的图中所看到的,任何正在等待的线程在被释放之前都不能使用锁定的对象。 # 改进目标 JEP143 的总体目标是提高 JVM 如何在锁定的 Java 对象监视器上管理争用的总体性能。对争用锁定的改进都是 JVM 内部的,不需要任何开发人员操作就可以从中获益。总体改进目标与更快的操作相关。其中包括: * 更快的监视器输入 * 更快的监视器退出 * 更快的通知 通知是当对象的锁定状态改变时调用的`notify()`和`notifyAll()`操作。测试这种改进并不是一件容易完成的事情。任何级别的更高的效率都是值得欢迎的,因此这一改进是值得我们感谢的。 # 分段代码缓存 Java 的分段代码缓存升级已经完成,结果是执行速度更快、效率更高。这一变化的核心是将代码缓存分割为三个不同的段:非方法段、概要代码段和非概要代码段。 代码缓存是 JVM 存储生成的本机代码的内存区域。 前面提到的每个代码缓存段都将保存特定类型的编译代码。如下图所示,代码堆区域按编译代码的类型进行分段: ![](img/6488fee8-2fe5-4389-8cb2-513751c09e3d.png) # 内存分配 包含非方法代码的代码堆用于 JVM 内部代码,由 3MB 固定内存块组成。其余的代码缓存内存平均分配给已分析代码和未分析代码段。您可以通过命令行命令对此进行控制。 以下命令可用于定义非方法编译代码的代码堆大小: ```java -XX:NonMethodCodeHeapSize ``` 以下命令可用于定义已分析编译方法的代码堆大小: ```java -XX:ProfiledCodeHeapSize ``` 以下命令可用于定义非概要编译方法的代码堆大小: ```java -XX:NonProfiledCodeHeapSize ``` 这个特性当然可以提高 Java 应用程序的效率。它还会影响使用代码缓存的其他进程。 # 智能 Java 编译 所有 Java 开发人员都将熟悉将源代码编译成字节码的工具,JVM 使用它来运行 Java 程序。智能 Java 编译,也称为智能 Javac 和`sjavac`,在`javac`进程周围添加了一个智能包装器。`sjavac`增加的核心改进可能是只重新编译必要的代码。在此上下文中,必要的代码是自上一个编译周期以来更改的代码。 如果开发人员只在小项目上工作,这种增强可能不会让他们感到兴奋。但是,考虑一下,当您不断地为中大型项目重新编译代码时,在效率方面的巨大收益。开发人员节省的时间足以让他们接受 JEP199。 这将如何改变编译代码的方式?它可能不会,至少现在不会,Javac 仍然是默认的编译器。尽管`sjavac`提供了增量构建的效率,但 Oracle 认为它没有足够的稳定性来成为标准编译工作流程的一部分。 # 解决 Lint 和 Doclint 警告 Lint 和 Doclint 是向`javac`报告警告的来源。我们来看看每一个: * Lint 分析`javac`的字节码和源代码。Lint 的目标是识别所分析代码中的安全漏洞。Lint 还可以深入了解可伸缩性和线程锁定问题。Lint 还有更多的功能,其总体目的是节省开发人员的时间。 [您可以在这里阅读更多关于 Lint 的信息](http://openjdk.java.net/jeps/212)。 * Doclint 与 Lint 类似,是针对`javadoc`的。Lint 和 Doclint 都报告编译过程中的错误和警告。这些警告的解决是 JEP212 的重点。使用核心库时,不应出现任何警告。这种思维方式导致了 JEP212,它已经在 Java9 中得到了解决和实现。 Lint 和 Doclint 警告的综合列表可以在 **JDK 错误系统**(**JBS**)中查看,可在[这个页面](https://bugs.openjdk.java.net)中获得。 # Javac 的分层属性 Javac 的类型检查已经简化了,让我们首先回顾一下 Java8 中的类型检查是如何工作的,然后我们将探讨现代 Java 平台中的变化。 在 Java8 中,poly 表达式的类型检查由推测属性工具处理 推测属性是一种类型检查方法,作为`javac`编译过程的一部分。它有很大的处理开销。 使用推测属性方法进行类型检查是准确的,但缺乏效率。这些检查包括参数位置,在递归、多态性、嵌套循环和 Lambda 表达式中进行测试时,速度会以指数级的速度减慢。因此,更新的目的是更改类型检查模式以创建更快的结果。结果本身并不是不准确的推测归因,他们只是没有迅速产生。 Java9-11 中提供的新方法使用了分层属性工具。此工具实现了一种分层方法,用于对所有方法调用的参数表达式进行类型检查。还为方法重写设置了权限。为了使此新架构正常工作,将为以下列出的每种类型的方法参数创建新的结构类型: * Lambda 表达式 * 多边形表达式 * 常规方法调用 * 方法引用 * 菱形实例创建表达式 对`javac`的修改比本节强调的更为复杂。对开发人员来说,除了效率更高和节省时间之外,没有什么直接的影响。 # 注释管道 2.0 Java 注释是指驻留在 Java 源代码文件中的一种特殊元数据。它们不会被`javac`剥离,因此它们可以在运行时对 JVM 保持可用。 注释看起来类似于 JavaDocs 引用,因为它们以`@`符号开头。注释有三种类型。让我们按如下方式检查每一项: * 注释的最基本形式是标记注释。这些是独立的注释,唯一的组件是动画的名称。举个例子: ```java @thisIsAMarkerAnnotation public double computeSometing(double x, double y) { // do something and return a double } ``` * 第二种类型的注释是包含一个值或一段数据的注释。正如您在下面的代码中所看到的,以`@`符号开头的注释后面是包含数据的圆括号: ```java @thisIsAMarkerAnnotation (data="compute x and y coordinates") public double computeSometing(double x, double y) { // do something and return a double } ``` 编码单值注释类型的另一种方法是省略`data=`组件,如以下代码所示: ```java @thisIsAMarkerAnnotation ("compute x and y coordinates") public double computeSometing(double x, double y) { // do something and return a double } ``` * 第三种类型的注释是当有多个数据组件时。对于这种类型的注释,`data=`组件不能省略。举个例子: ```java @thisIsAMarkerAnnotation (data="compute x and y coordinates", purpose="determine intersecting point") public double computeSometing(double x, double y) { // do something and return a double } ``` 那么,Java9、10 和 11 中发生了什么变化?要回答这个问题,我们需要回顾一下 Java8 引入的几个影响 Java 注释的更改: * Lambda 表达式 * 重复注释 * Java 类型注释 这些与 Java8 相关的更改影响了 Java 注释,但并没有改变`javac`处理它们的方式。有一些硬编码的解决方案允许`javac`处理新的注释,但它们效率不高。此外,这种类型的编码(硬编码解决方法)很难维护。 因此,JEP217 专注于重构`javac`注释管道。这种重构都是`javac`内部的,所以对开发人员来说应该不明显。 # 新版本字符串方案 在 Java9 之前,版本号没有遵循行业标准的版本控制语义版本控制。例如,最后四个 JDK8 版本如下: * Java SE 8 更新 144 * Java SE 8 更新 151 * Java SE 8 更新 152 * Java SE 8 更新 161 * Java SE 8 更新 162 **语义版本控制**使用主要、次要、补丁(`0.0.0`)模式,如下所示: * **主要**等同于不向后兼容的新 API 更改 * **次要**是添加向后兼容的功能时 * **补丁**是指错误修复或向后兼容的小改动 Oracle 从 Java9 开始就支持语义版本控制。对于 Java,Java 版本号的前三个元素将使用主次安全模式: * **主要**:由一组重要的新特性组成的主要版本 * **次要**:向后兼容的修订和错误修复 * **安全**:被认为是提高安全性的关键修复 Java9 有三个版本:初始版本和两个更新。下面列出的版本演示了主要的次要安全模式: * Java SE 9 * Java SE 9.0.1 版 * Java SE 9.0.4 版 如第 1 章、“Java11 场景”所述,在 Java9 之后的版本将遵循*的时间发布模式年月日*。使用该模式,Java9 之后的四个版本如下: * Java SE 18.3(2018 年 3 月) * Java SE 18.9(2018 年 9 月) * Java SE 19.3(2019 年 3 月) * Java SE 19.9(2019 年 9 月) # 自动生成运行时编译器测试 Java 可以说是最常用的编程语言,并且驻留在越来越多样化的平台上。这加剧了以有效方式运行目标编译器测试的问题。新的 Java 平台包括一个自动化运行时编译器测试的工具。 这个新工具首先生成一组随机的 Java 源代码和/或字节码。生成的代码将具有三个关键特征: * 它在语法上是正确的 * 它在语义上是正确的 * 它将使用一个随机种子,允许重用相同的随机生成的代码 随机生成的源代码将保存在以下目录中: ```java hotspot/test/testlibrary/jit-tester ``` 这些测试用例将被存储起来以供以后重用。它们可以从`j-treg`目录或工具的 makefile 运行。重新运行保存的测试的好处之一是测试系统的稳定性。 # 测试 Javac 生成的类文件属性 缺乏或不足以为类文件属性创建测试的能力是确保`javac`完全正确地创建类文件属性的动力。这表明,即使某些属性没有被类文件使用,所有类文件都应该生成一组完整的属性。还需要有一种方法来测试类文件是否根据文件的属性正确创建 在 Java9 之前,没有测试类文件属性的方法。运行类并测试代码以获得预期的或预期的结果是测试`javac`生成的类文件最常用的方法。这种技术无法通过测试来验证文件的属性。 JVM 使用的类文件属性有三类:可选属性和 JVM 不使用的属性。 JVM 使用的属性包括: * `BootstrapMethods` * `Code` * `ConstantValue` * `Exceptions` * `StackMapTable` 可选属性包括: * `Deprecated` * `LineNumberTable` * `LocalVariableTable` * `LocalVariableTypeTable` * `SourceDebugExtension` * `SourceFile` JVM 未使用的属性包括: * `AnnotationDefault` * `EnclosingMethod` * ``InnerClasses`` * `MethodParameters` * `RuntimeInvisibleAnnotations` * `RuntimeInvisibleParameterAnnotations` * `RuntimeInvisibleTypeAnnotations` * `RuntimeVisibleAnnotations` * `RuntimeVisibleParameterAnnotations` * `RuntimeVisibleTypeAnnotations` * `Signature` * `Synthetic` # 在类数据共享档案中存储内部字符串 在 Java5 到 Java5 中,存储字符串并从 CDS 存档中访问字符串的方法效率低下,非常耗时,而且浪费了内存。下图说明了 Java 在 Java9 之前将内部字符串存储在 CD 存档中的方法: ![](img/938faf92-cb4f-4fc8-98d2-265e234673ad.png) 低效率源于存储模式。当 CDS 工具将类转储到共享存档文件中时,这一点尤为明显。包含`CONSTANT_String`项的常量池具有 UTF-8 字符串表示。 UTF-8 是一种 8 位可变长度字符编码标准。 # 问题 在 Java9 之前使用 UTF-8 时,字符串必须转换为字符串对象,即`java.lang.String`类的实例。这种转换是按需进行的,这通常会导致系统速度变慢和不必要的内存使用。处理时间非常短,但内存使用过多。一个内部字符串中的每个字符都需要至少 3 个字节的内存,甚至更多。 一个相关的问题是,并非所有 JVM 进程都可以访问存储的字符串。 # Java9 解决方案 CDS 存档从 Java9 开始,在堆上为字符串分配特定的空间。该过程如下图所示: ![](img/d47d308f-f085-43e8-831d-b5ca4f955d93.png) 使用共享字符串表、哈希表和重复数据消除映射字符串空间。 **数据去重**是一种数据压缩技术,可消除档案中的重复信息。 # Java10 的改进 Java9 引入了更高效的 cd,Java9 进一步改进了这个特性,特别是支持将应用程序类添加到共享存档中。JEP310 应用程序 cd 的目的不是为了使归档文件膨胀、启动时间变慢或消耗超过需要的内存。尽管如此,如果不对 CDS 采取有目的的方法,这些结果是可能的 我们对 CDS 存档使用三个步骤:确定要包含的类、创建存档和使用存档: 1. 阶级决定 2. AppCD 存档创建 3. 使用 AppCD 存档 让我们检查一下每一步的细节。 # 阶级决定 使用 cd 的最佳实践是只归档所使用的类。这将有助于防止档案不必要地膨胀。我们可以使用以下命令行和标志来确定加载了哪些类: ```java java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=ch2.lst - cp cp2.jar Chapter2 ``` # AppCD 存档创建 一旦我们知道加载了哪些类,我们就可以创建 AppCDS 存档。以下是要使用的命令行和标志选项: ```java java -Xshare:dump -XX:+UseApsCDS \ -XX:SharedClassListFile=ch2.lst \ -XX:SharedArchiveFile=ch2.jsa -cp ch2.jar ``` # 使用 AppCD 存档 为了使用 AppCDS 存档,我们发出`-Xshare:on`命令行选项,如下所示: ```java java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=ch2.jsa -cp ch2.jar Chapter2 ``` # 为模块化准备 JavaFXUI 控件和级联样式表 API JavaFX 是一组允许设计和开发富媒体图形用户界面的软件包。JavaFX 应用程序为开发人员提供了一个很好的 API,用于为应用程序创建一致的接口。**级联样式表**(**CSS**)可用于定制接口。JavaFX 的一个优点是编程和接口设计的任务可以很容易地分开。 # JavaFX 概述 JavaFX 包含一个很棒的可视化脚本工具场景构建器,它允许您使用拖放和属性设置来创建图形用户界面。场景生成器生成 IDE 使用的必要 FXML 文件,例如 NetBeans。 以下是使用场景生成器创建的示例 UI: ![](img/07a369be-63f8-4cd1-a34b-18e1c9e676b2.png) 下面是场景生成器创建的 FXML 文件: ```java