Skip to content

Files

Latest commit

eb0ea53 · Sep 21, 2021

History

History
481 lines (334 loc) · 16.9 KB

File metadata and controls

481 lines (334 loc) · 16.9 KB

十四、命令行标志

在上一章中,我们研究了 JDK 的几个安全性更改。Java 的安全增强为开发人员提供了编写和维护实现安全性的应用的能力。更具体地说,我们讨论了数据报传输层安全性、密钥库、提高安全应用性能、TLS ALPN、利用 GHASH 和 RSA 的 CPU 指令、TLS 的 OCSP 装订以及基于 DRBG 的SecureRandom实现。

在本章中,我们将探讨现代 Java 平台的几个变化,这些变化的共同主题是命令行标志。更具体地说,我们将介绍以下概念:

  • 统一 JVM 日志记录
  • 编译器控件
  • 诊断命令
  • 堆分析代理
  • 移除 JHAT
  • 命令行标志参数验证
  • 为旧平台版本编译
  • 基于 Java 的 JIT 编译器实验

技术要求

本章及后续章节主要介绍 Java11,Java 平台的标准版SE)可从 Oracle 官方网站下载。

IDE 包就足够了。来自 JetBrains 的 IntelliJ IDEA 用于与本章和后续章节相关的所有编码。IntelliJ IDEA 的社区版可从网站下载。

统一 JVM 日志记录

Java9 中引入了为 JVM 创建统一的日志模式。以下是这项工作的目标的综合清单:

  • 为所有日志操作创建一组 JVM 范围的命令行选项

  • 使用分类标签进行日志记录

  • 允许消息具有多个标签,也称为标签集

  • 要提供六个级别的日志记录:

  • 错误

  • 警告

  • 信息

  • 调试

  • 跟踪

  • 开发

  • 根据级别选择要记录的消息

  • 要选择性地将日志记录定向到控制台或文件,请执行以下操作:

  • 一次打印一行,不支持在同一行内交错

  • 允许输出多行日志(非交错)

  • 设置所有日志消息的格式,使其易于人阅读

  • 添加装饰,如正常运行时间、级别和标记

  • 与级别类似,用于选择基于装饰记录哪些消息

  • 将 Java9 之前的tty>print日志转换为使用统一日志作为输出

  • 允许使用jcmdMBeans进行动态消息配置

  • 允许启用和禁用单个日志消息

  • 添加确定装饰打印的顺序的功能

对 JVM 的统一日志记录更改可以分为以下五类:

  • 命令行选项
  • 装饰
  • 水平
  • 输出
  • 标签

让我们简单地看一下这些类别中的每一个。

命令行选项

新的命令行选项-Xlog是 Java 日志框架的关键组件。这个命令行选项有大量的参数和可能性。基本语法是-Xlog,后跟一个选项。

以下是正式的基本语法:

-Xlog[:option]

下面是一个带有all选项的基本示例:

-Xlog:all

以下是用于配置新的统一日志记录的广泛命令行语法:

-Xlog[:option]
option := [<what>][:[<output>][:[<decorators>][:<outputoptions>]]]
'help'
'disable' 
what := <selector>[,...]
selector := <tag-set>[*][=<level>]
tag-set := <tag>[+..]
'all'
tag := name of tag
level := trace
debug
info
warning
error 
output := 'stderr'
'stdout'
[file=]<filename>
decorators := <decorator>[,...]
'none' 
decorator := time
uptime
timemillis
uptimemillis
timenanos
uptimenanos
pid
tid
level
tags
output-options := <output_option>[,...]
output-option := filecount=<file count>
filesize=<file size in kb>
parameter=value

以下-Xlog示例后面是说明:

-Xlog:all

在前面的示例中,我们告诉 JVM 执行以下操作:

  • 记录所有消息
  • 使用info水平
  • stdout提供输出

在本例中,所有的warning消息仍将输出到stderr

以下示例在debug级别记录消息:

-Xlog:gc+rt*=debug

在前面的示例中,我们告诉 JVM 执行以下操作:

  • 记录至少带有gcrt标记的所有消息
  • 使用debug水平
  • stdout提供输出

以下示例将输出推送到外部文件:

-Xlog:disable - Xlog:rt=debug:rtdebug.txt

在前面的示例中,我们告诉 JVM 执行以下操作:

  • 禁用除标记有rt标记的消息以外的所有消息
  • 使用debug水平
  • 向名为rtdebug.txt的文件提供输出

装饰

在 Java 日志框架的上下文中,装饰是关于日志消息的元数据。以下是按字母顺序排列的可用装饰品列表:

  • level:与记录的消息相关联的级别
  • pid:进程标识符
  • tags:与记录的消息相关联的标签集
  • tid:线程标识符
  • time:指当前日期和时间,采用 ISO-8601 格式
  • timemillis:当前时间(毫秒)
  • timenanos:当前时间(纳秒)
  • uptime:JVM 启动后的时间,以秒和毫秒为单位
  • uptimemillis:JVM 启动后的时间,以毫秒为单位
  • uptimenanos:JVM 启动后的时间,以纳秒为单位

装饰可以超越或包含在统一的日志输出中。无论使用哪种装饰,它们都将按以下顺序出现在输出中:

  1. time
  2. uptime
  3. timemillis
  4. uptimemillis
  5. timenanos
  6. uptimenanos
  7. pid
  8. tid
  9. level
  10. tags

级别

记录的消息单独与详细级别相关联。如前所述,级别为错误警告信息调试跟踪开发。下表显示了这些级别相对于记录的信息量的详细程度是如何增加的。“开发级别”仅用于开发目的,在产品应用内部版本中不可用:

日志消息的详细级别

使用 Xlog 输出

Java 日志框架支持三种类型的输出,包括直接使用-Xlog命令行语法的示例:

在下面的示例中,我们向stderr提供输出:

-Xlog:all=warning:stderr:none

下面的示例向stdout提供输出:

-Xlog:all=warning:stdout:none

以下示例将输出写入文本文件:

-Xlog:all=warning:file=logmessages.txt:none

标签

新的日志框架由一组在 JVM 中标识的标记组成。如果需要,可以在源代码中更改这些标记。标签应该是自识别的,例如用于垃圾收集的gc

当多个标记组合在一起时,它们形成一个标记集。当我们通过源代码添加自己的标记时,每个标记都应该与一个标记集相关联。这将有助于确保标签保持有序,并且易于人类阅读。

编译器控制

控制 Java 虚拟机JVM)编译器似乎是一项不必要的任务,但对于许多开发人员来说,这是测试的一个重要方面。这是通过依赖于方法的编译器标志实现的。

在本节中,我们将从 JVM 编译模式开始,然后看看可以使用 Java 平台控制的编译器。

编译模式

现代 Java 平台的变化包括对 JVM 编译器的细粒度控制。如下图所示,Java HotSpot JVM 有两种 JIT 编译模式 C1C2

Java HotSpot JVM 编译模式

C1C2 编译模式使用不同的编译技术,如果在同一个代码基上使用,可以产生不同的机器代码集。

C1 编译模式

Java HotSpot VM 中的 C1 编译模式通常用于具有以下特征的应用:

  • 快速启动
  • 增强优化
  • 客户端

C2 编译模式

第二种编译模式 C2 由具有下列特征的应用使用:

  • 长运行时间
  • 服务器端

分层编译

分层编译允许我们同时使用 C1C2 编译模式。从 Java8 开始,分层编译是默认的过程。如图所示,启动时使用 C1 模式有助于提供更大的优化。然后,一旦 App 充分预热,则采用 C2 模式:

分层编译

Java11 中的编译器控制

Java 承诺能够有限地控制 JVM 编译器并在运行时进行更改。这些额外的能力不会降低性能。这使得测试和测试优化更加逼真,因为我们可以运行小型编译器测试,而不必重新启动整个 JVM。

为了控制编译器操作,我们需要创建一个指令文件。这些文件包含由一组带有值的选项组成的编译器指令。指令文件基本上使用 JSON 的一个子集:

编译器指令结构

JavaScript 对象表示法JSON)格式用于数据交换。指令文件与 JSON 有以下格式差异:

  • intdoubles是唯一支持的数字格式
  • 双正斜杠(//)可用于注释行
  • 尾随逗号(,)可用于数组和对象中
  • 不支持转义字符
  • 选项名的格式为字符串,不必加引号

您可以在这个页面了解更多关于 JSON 的信息。

我们可以在命令行中使用以下语法添加指令文件:

-XX:CompilerDirectivesFile=<file>

以下是指令文件的 Shell 示例:

[ // Open square bracket marks the start of the directives file

{ // Open curly brace marks the start of a directive block
  // A directives block that applies specifically to the C1 mode
  c1: {
        // directives go here
      },

  // A directives block that applies specifically to the C2 mode
  c2: {
        // directives go here
      },

  // Here we can put a directives that do not apply to
  // a specific compiler mode
},

{ // can have multiple directive blocks

  c1: {
        // directives go here
      }

  c2: {
        // directives go here
      }
}
] // Close square bracket marks the start of the directives file

诊断命令

在现代 Java 平台中添加了七个新的诊断命令,以增强诊断 JDK 和 JVM 的能力。

print_codegenlist命令打印当前排队等待编译的方法。由于 C1 和 C2 编译模式位于不同的队列上,因此需要向特定队列发出此命令。

dump_codelist诊断命令将打印编译方法的下列信息:

  • 完整签名
  • 地址范围
  • 状态:
  • 活着
  • 非参与
  • 僵尸

此外,dump_codelist诊断命令允许将输出定向到stdout或指定的文件。输出可以是 XML 格式或标准文本。

print_codeblocks命令允许我们打印以下内容:

  • 代码缓存大小
  • 代码缓存列表
  • 代码缓存中的块列表
  • 代码块地址

datadump_request诊断命令向 Java 虚拟机工具接口JVMTI)发送转储请求。它取代了 Java 虚拟机调试接口JVMDI)和 Java 虚拟机评测接口JVMPI)。

使用set_vmflag命令,我们可以在 JVM 或库中设置命令行标志或选项。

print_class_summary诊断命令打印所有加载类的列表及其继承结构。

print_utf8pool命令打印所有 UTF-8 字符串常量。

堆性能分析代理

JVMTI hprof代理最近从 Java 平台上删除了。以下是与此更改相关的关键术语:

  • 工具接口TI):这是一个本机编程接口,允许工具控制正在 Java 虚拟机内运行的应用的执行。该接口还允许状态查询。这个工具的完整术语是 Java 虚拟机工具接口,或 JVMTI。
  • 堆性能测试HPROF):这是一个内部 JDK 工具,用于分析 JVM 对 cpu 和堆的使用。开发人员最常见的暴露是崩溃后生成的文件。生成的文件包含堆转储。

Java11JDK 不包含hprof代理。它被删除主要是因为有更好的替代品可用。以下是它们的相关功能表:

HPROF 功能 备选方案
分配探查器(堆=站点) Java 可视化
CPU 档案器(CPU=样本) Java VisualVM
(CPU=次数) Java 飞行记录器
堆转储(Heap=dump) 内部 JVM 功能:
GC.heap_dump(icmd <pid> GC.heap_dump)
jmap -dump

有趣的是,最初创建 HPROF 时,并不打算在生产中使用它。实际上,它只是为了测试 JVM 工具接口的代码。因此,随着现代 Java 平台的出现,HPROF 库(libhprof.so将不再是 JDK 的一部分。

移除 JHAT

Java 堆分析工具JHAT)用于解析 Java 堆转储文件。此堆转储文件解析工具的语法如下:

jhat
    [-stack <bool>]
    [-refs <bool>]
    [-port <port>]
    [-baseline <file>]
    [-debug <int>]
    [-version]
    [-h|-help]
   <file>

下面简要介绍与 JHAT 命令相关的选项:

选项 说明 默认值
-J<flag> 这会将<flag>传递给运行时系统 不适用
-stack<bool> 这将切换对象分配调用栈的跟踪 true
-refs<bool> 这将切换对对象引用的跟踪 true
-port<port> 这表示 JHAT HTTP 服务器的端口 7000
-exclude<exclude-filename> 这将从可访问对象中排除指定的文件 不适用
-baseline<filename> 这将指定用于比较的基准堆转储 不适用
-debug<int> 这将设置输出的详细程度 不适用
-version  这只是输出 JHAT 版本号 不适用
-h -help 这将提供帮助文本 不适用

JHAT 从 JDK-6 开始就以实验的形式成为 Java 平台的一部分。它不受支持,被认为是过时的。从 Java9 开始,这个工具不再是 JDK 的一部分。

命令行标志参数验证

在本章中,您已经了解了 Java 平台中命令行标志的许多用法。一致努力确保所有带参数的 JVM 命令行标志都得到验证。这项工作的主要目标是:

  • 避免 JVM 崩溃
  • 提供错误消息来告诉你无效的标志参数

从下图中可以看到,没有尝试自动更正标志参数错误;相反,只是为了识别错误并防止 JVM 崩溃:

标志参数错误

这里提供了一个示例错误消息,指出标志参数超出范围。此错误将在 JVM 初始化期间执行的标志参数范围检查期间显示:

exampleFlag UnguardOnExecutionViolation = 4 is outside the allowed range [0 . . . 3]

以下是一些有关 Java 平台更改的细节:

  • 展开当前的globals.hpp源文件,以确保完整的标志默认值和允许的范围被记录

  • 定义一个框架以支持将来添加新的 JVM 命令行标志:

  • 这将包括值范围和值集

  • 这将确保有效性检查将应用于所有新添加的命令行标志

  • 修改宏表:

  • 为可选范围添加最小值/最大值

  • 为以下项添加约束项:

    • 确保每次标记更改时都执行约束检查
    • 当 JVM 运行时,将继续检查所有可管理的标志

为旧平台版本编译

Java 编译器javac在 Java9 中进行了更新,以确保它可以用来编译 Java 程序,以便在用户选择的旧版本 Java 平台上运行。在下面的截图中可以看到,javac有几个选项,包括-source-target。以下截图中显示的javac来自 Java8:

Java8 中的 Javac 选项

-source选项用于指定编译器接受的 Java 版本。-target选项通知您将生成哪个版本的类文件javac。默认情况下,javac生成最新 Java 版本和平台 API 版本的类文件。当编译的应用使用仅在最新平台版本中可用的 API 时,这可能会导致问题。这将导致应用无法在较旧的平台版本上运行,尽管使用了-source-target选项。

为了解决上述问题,Java 中提供了一个新的命令行选项,这个选项是--release选项,当使用这个选项时,会自动配置javac来生成与特定平台版本相链接的类文件。下面的屏幕截图显示了当前 Java 平台的javac选项。如您所见,新的--release选项包括:

Java18.9 中的 Javac 选项

以下是新选项的语法:

javac --release <release> <source files>

基于 Java 的实验性 JIT 编译器

在 Java10 中启用了基于 Java 的即时(JIT)编译器,可以作为 Linux/x64 平台的实验性 JIT 编译器。基于 Java 的 JIT 编译器被称为 Graal

做出这一更改的目的是希望通过实验可以证明将 JIT 编译器添加到 JDK 中的概念

总结

在本章中,我们探讨了现代 Java 平台的一些变化,这些变化的共同主题是命令行标志。具体来说,我们讨论了统一 JVM 日志记录、编译器控制、新的诊断命令、HPROF 堆分析代理的删除、JHAT 的删除、命令行标志参数验证,以及针对旧平台版本进行编译的能力。

在下一章中,我们将重点介绍 Java 中提供的附加工具的最佳实践。其中包括 UTF-8、Unicode 7.0、Linux 等等。

问题

  1. Java9 中引入的 JVM 日志记录模式是什么?

  2. 日志记录的五个类别是什么?

  3. 什么是装饰?

  4. 日志中的详细程度是多少?

  5. 哪一个详细级别是最高的?

  6. 哪种详细程度最低?

  7. 如何更改日志标记?

  8. 什么用于控制 JVM?

  9. Java HotSpot JVM 的 JIT 编译模式是什么?

  10. 哪种编译模式具有快速启动功能?

进一步阅读

以下是您可以参考的信息列表:

  • 《Java SE 9 整洁代码入门》【视频】在这个页面提供。