提交 169b8762 编写于 作者: W wizardforcel

2021-10-01 23:06:35

上级 3501380a
......@@ -18,7 +18,7 @@
第 4 课“微服务”描述了许多行业领导者在解决负载下的灵活扩展时所采用的解决方案。它讨论了通过将应用程序拆分为几个微服务来添加更多工作人员,每个微服务独立部署,每个微服务使用多线程和反应式编程,以获得更好的性能、响应、可扩展性和容错性。
第 5 课“利用新 API 改进代码”描述了编程工具的改进,包括流过滤器、堆栈遍历 API、创建不可变集合的新的方便静态工厂方法、支持异步处理的新的强大 CompletableFuture 类,以及 JDK9 流 API 的改进。
第 5 课“利用新 API 改进代码”描述了编程工具的改进,包括流过滤器、堆栈遍历 API、创建不可变集合的新的方便静态工厂方法、支持异步处理的新的强大`CompletableFuture`类,以及 JDK9 流 API 的改进。
# 我能从这本书中得到什么?
......
......@@ -45,7 +45,7 @@ $java --list-modules java.base
![Modular Development and Its Impact](img/01_02.jpg)
前面的命令将以包的形式显示来自基本模块的所有导出。javabase 是系统的核心。
前面的命令将以包的形式显示来自基本模块的所有导出。`java.base`是系统的核心。
这将显示所有图形用户界面包。这也将显示`requires`哪些是依赖项:
......@@ -159,7 +159,7 @@ public Class StringProcessor{
# 串操作性能
若你们不是编程新手,那个么到目前为止,string 一定是你们最好的朋友。在许多情况下,你可能比你的配偶或伴侣更喜欢它。我们都知道,没有字符串是无法生存的,事实上,如果不使用字符串,您甚至无法完成应用程序。好了,关于字符串已经说得够多了,我已经对字符串的使用感到头晕目眩,就像早期版本中的 JVM 一样。除了笑话之外,让我们来谈谈 Java 9 中的哪些变化将帮助您的应用程序更好地执行。虽然这是一个内部变化,但作为一名应用程序开发人员,理解这一概念非常重要,这样您就可以知道在哪里重点改进性能。
若你们不是编程新手,那个么到目前为止,字符串一定是你们最好的朋友。在许多情况下,你可能比你的配偶或伴侣更喜欢它。我们都知道,没有字符串是无法生存的,事实上,如果不使用字符串,您甚至无法完成应用程序。好了,关于字符串已经说得够多了,我已经对字符串的使用感到头晕目眩,就像早期版本中的 JVM 一样。除了笑话之外,让我们来谈谈 Java 9 中的哪些变化将帮助您的应用程序更好地执行。虽然这是一个内部变化,但作为一名应用程序开发人员,理解这一概念非常重要,这样您就可以知道在哪里重点改进性能。
Java9 朝着提高字符串性能迈出了一步。如果您曾经遇到 JDK 6 的失败尝试`UseCompressedStrings`,那么您必须寻找提高字符串性能的方法。因为`UseCompressedStrings`是一个实验性的特性,容易出错,而且设计得不是很好,所以它在 JDK 7 中被删除。不要为此感到难过,我知道这很糟糕,但黄金岁月终将到来。JEP 团队经历了巨大的痛苦,添加了一个紧凑的字符串特性,可以减少字符串及其相关类的占用空间。
......@@ -181,18 +181,18 @@ Java9 朝着提高字符串性能迈出了一步。如果您曾经遇到 JDK 6
## 为什么要压缩字符串?
现在您对 heap 有了一点了解,让我们看看`String`类以及字符串是如何在 heap 上表示的。如果您仔细分析应用程序的堆,您会注意到有两个对象,一个是 Java 语言`String`对象,它引用了实际处理数据的第二个对象`char[]``char`数据类型为 UTF-16,因此最多占用 2 个字节。让我们看一下以下两个不同语言字符串的外观示例:
现在您对堆有了一点了解,让我们看看`String`类以及字符串是如何在堆上表示的。如果您仔细分析应用程序的堆,您会注意到有两个对象,一个是 Java 语言`String`对象,它引用了实际处理数据的第二个对象`char[]``char`数据类型为 UTF-16,因此最多占用 2 个字节。让我们看一下以下两个不同语言字符串的外观示例:
```java
2 byte per char[]
Latin1 String : 1 byte per char[]
```
您可以看到,`Latin1 String`只消耗 1 字节,因此我们损失了大约 50%的空间。有机会以更密集的形式表示它,并改进足迹,这最终将有助于加快垃圾收集。
您可以看到,`Latin1 String`只消耗 1 字节,因此我们损失了大约 50% 的空间。有机会以更密集的形式表示它,并改进足迹,这最终将有助于加快垃圾收集。
现在,在对此进行任何更改之前,了解它对实际应用程序的影响非常重要。必须知道应用程序是每`char[]`字符串使用 1 字节还是每`char[]`字符串使用 2 字节。
为了得到答案,JPM 团队分析了大量实际数据的堆转储。结果突出显示,大多数堆转储占`chars[]`消耗的整个堆的 18%到 30%左右,它们来自字符串。此外,值得注意的是,大多数字符串由每个`char[]`的一个字节表示。因此,很明显,如果我们试图改善单字节字符串的占用空间,它将显著提高许多实际应用程序的性能。
为了得到答案,JPM 团队分析了大量实际数据的堆转储。结果突出显示,大多数堆转储占`chars[]`消耗的整个堆的 18% 到 30% 左右,它们来自字符串。此外,值得注意的是,大多数字符串由每个`char[]`的一个字节表示。因此,很明显,如果我们试图改善单字节字符串的占用空间,它将显著提高许多实际应用程序的性能。
### 他们做了什么?
......@@ -202,7 +202,7 @@ Latin1 String : 1 byte per char[]
### 逃生路线是什么?
尽管听起来很棒,但如果每个`char[]`字符串只使用 2 个字节,可能会影响应用程序的性能。在这种情况下,不使用前面提到的、检查并直接将字符串存储为每`char[]`2 个字节是有意义的。因此,JPM 团队提供了一个 kill 开关`--XX: -CompactStrings`,您可以使用它禁用此功能。
尽管听起来很棒,但如果每个`char[]`字符串只使用 2 个字节,可能会影响应用程序的性能。在这种情况下,不使用前面提到的、检查并直接将字符串存储为每`char[]`2 个字节是有意义的。因此,JPM 团队提供了一个杀死它的开关`--XX: -CompactStrings`,您可以使用它禁用此功能。
### 性能增益是多少?
......@@ -261,7 +261,7 @@ public static getMyAwesomeString()Ljava/lang/String;
但是,在运行时,由于在 JIT 编译器中包含了`-XX:+-OptimizeStringConcat`,它现在可以识别`StringBuilder``toString`链的附加。如果识别出匹配项,则生成用于优化处理的低级代码。计算所有参数的长度,计算出最终容量,分配存储,复制字符串,并执行原语的就地转换。在此之后,将此阵列移交给`String`实例,无需复制。这是一个有利可图的优化。
但这在连接方面也有一些缺点。一个例子是,在使用 long 或 double 连接字符串的情况下,它将无法正确优化。这是因为编译器必须先执行`.getChar`,这会增加开销。
但这在连接方面也有一些缺点。一个例子是,在使用`long``double`连接字符串的情况下,它将无法正确优化。这是因为编译器必须先执行`.getChar`,这会增加开销。
另外,如果你在`String`后面加上`int`,那么它的效果非常好;但是,如果您有一个像`i++`这样的增量运算符,那么它将中断。这背后的原因是您需要返回到表达式的开头并重新执行,因此实际上您要执行两次`++`。现在是 Java9 压缩字符串中最重要的变化。像`value.length >> coder`这样的长度拼写;`C2`无法优化,因为它不知道 IR。
......@@ -327,7 +327,7 @@ InvokeDynamic #0: makeConcat(String, int, long)
4. 分配`byte[]`存储,然后复制所有参数。然后,就地转换基本体。
5. 通过移交数组进行连接来调用私有构造函数`String`
这样,我们就可以在相同的代码中获得优化的字符串 concat,而不是在`C2 IR`中。此策略使我们的性能提高了 2.9 倍,垃圾减少了 6.4 倍。
这样,我们就可以在相同的代码中获得优化的字符串连接,而不是在`C2 IR`中。此策略使我们的性能提高了 2.9 倍,垃圾减少了 6.4 倍。
# 在 CD 档案中存储插入的字符串
......@@ -392,7 +392,7 @@ Java9 改进了争用锁定。您可能想知道什么是竞争锁定。让我
编译器改进的第二个显著变化是提前编译。如果您不熟悉这个术语,让我们看看 AOT 是什么。您可能知道,任何语言中的每个程序都需要运行时环境来执行。Java 也有自己的运行时,称为**Java 虚拟机****JVM**)。我们大多数人使用的典型运行时是字节码解释器,它也是 JIT 编译器。此运行时称为**HotspotJVM**
这个HotspotJVM 以通过 JIT 编译和自适应优化提高性能而闻名。到现在为止,一直都还不错。然而,这在实践中并不适用于每个应用程序。如果您有一个非常轻的程序,比如说,一个方法调用,该怎么办?在这种情况下,JIT 编译对您帮助不大。你需要一些能更快加载的东西。这就是 AOT 将帮助您的地方。使用 AOT 而不是 JIT,您可以编译为本机机器代码,而不是编译为字节码。然后,运行时使用此本机代码管理对 malloc 中新对象的调用以及对系统调用的文件访问。这可以提高性能。
这个HotspotJVM 以通过 JIT 编译和自适应优化提高性能而闻名。到现在为止,一直都还不错。然而,这在实践中并不适用于每个应用程序。如果您有一个非常轻的程序,比如说,一个方法调用,该怎么办?在这种情况下,JIT 编译对您帮助不大。你需要一些能更快加载的东西。这就是 AOT 将帮助您的地方。使用 AOT 而不是 JIT,您可以编译为本机机器代码,而不是编译为字节码。然后,运行时使用此本机代码管理对`malloc`中新对象的调用以及对系统调用的文件访问。这可以提高性能。
# 安全管理器改进
......@@ -402,13 +402,13 @@ Java9 改进了争用锁定。您可能想知道什么是竞争锁定。让我
对于 web 小程序,浏览器提供安全管理器,或者 Java web Start 插件运行此策略。在许多情况下,web 小程序以外的应用程序运行时没有安全管理器,除非这些应用程序实现了安全管理器。毫无疑问,如果没有安全管理器和附加的安全策略,那么应用程序的行为将不受限制。
现在我们对安全管理器有了一点了解,让我们看看这方面的性能改进。根据 Java 团队的说法,在安装了 security manager 的情况下运行的应用程序可能会降低 10%到 15%的性能。但是,不可能消除所有性能瓶颈,但缩小这一差距不仅有助于提高安全性,而且有助于提高性能。
现在我们对安全管理器有了一点了解,让我们看看这方面的性能改进。根据 Java 团队的说法,在安装了安全管理器的情况下运行的应用程序可能会降低 10% 到 15% 的性能。但是,不可能消除所有性能瓶颈,但缩小这一差距不仅有助于提高安全性,而且有助于提高性能。
Java9 团队研究了一些优化,包括安全策略的实施和权限的评估,这将有助于提高使用安全管理器的总体性能。在性能测试阶段,有人强调,尽管权限类是线程安全的,但它们显示为热点。为了减少线程争用和提高吞吐量,已经进行了许多改进。
计算`java.security.CodeSource``hashcode`方法已得到改进,以使用代码源 URL 的字符串形式,避免潜在的昂贵 DNS 查找。此外,对包含包检查算法的`java.lang.SecurityManager``checkPackageAccess`方法进行了改进。
security manager 改进中的其他一些值得注意的变化如下:
安全管理器改进中的其他一些值得注意的变化如下:
* 第一个明显的变化是使用`ConcurrentHashMap`代替`Collections.synchronizedMap`有助于提高`Policy.implie`方法的吞吐量。 看下图,取自 OpenJDK 站点,它突出显示了使用`ConcurrentHashMap`显着增加的吞吐量:
......
......@@ -2,13 +2,13 @@
自从编程作为一种职业出现以来,每一个有抱负的程序员的长期目标都是快速生成能够以闪电般的速度执行指定任务的应用程序。否则,何必麻烦呢?我们可以慢慢地做我们几千年来一直在做的事情。在上个世纪的书中,我们在这两个方面都取得了实质性的进展,现在,Java9 在这两个方向上又迈出了一步。
Java 9 中引入了两个新工具,JShell 和提前编译(**AOT**)的编译器——这两个工具都被期待了很长一段时间。JShell 是一个**读取–评估–打印循环****REPL**)工具,对于那些使用 Scala、Ruby 或 Python 编程的人来说,它是众所周知的。它接受用户输入,对其求值,并立即返回结果。AOT 编译器接受 Java 字节码并生成本机(依赖于系统)机器代码,以便生成的二进制文件可以本机执行。**
Java 9 中引入了两个新工具,JShell 和提前编译(**AOT**)的编译器——这两个工具都被期待了很长一段时间。JShell 是一个**读取–评估–打印循环****REPL**)工具,对于那些使用 Scala、Ruby 或 Python 编程的人来说,它是众所周知的。它接受用户输入,对其求值,并立即返回结果。AOT 编译器接受 Java 字节码并生成本机(依赖于系统)机器代码,以便生成的二进制文件可以本机执行。
**这些工具将是本课程的重点。
# JShell 工具的使用
JShell 帮助程序员在编写代码片段时测试它们。它避免了开发周期中的构建-部署测试部分,从而缩短了开发时间。程序员可以轻松地将一个表达式甚至几个方法复制到 JShell 会话中,并立即多次运行 test-modify。这样的快速转换还有助于在使用库 API 之前更好地理解库 API,并优化代码以准确表达其目的,从而促进更高质量的软件。
JShell 帮助程序员在编写代码片段时测试它们。它避免了开发周期中的构建-部署测试部分,从而缩短了开发时间。程序员可以轻松地将一个表达式甚至几个方法复制到 JShell 会话中,并立即多次运行测试-更改。这样的快速转换还有助于在使用库 API 之前更好地理解库 API,并优化代码以准确表达其目的,从而促进更高质量的软件。
我们多久会猜到特定 API 的 JavaDoc 意味着什么,并浪费构建部署测试周期来弄清楚它?或者我们想回忆一下,`substring(3)`将如何分割字符串?有时,我们创建一个小型测试应用程序,在其中运行我们不确定的代码,再次使用相同的构建-部署测试周期。使用 JShell,我们可以复制、粘贴和运行。在本节中,我们将描述并演示如何执行此操作。
......@@ -244,7 +244,7 @@ java -XX:CompileThreshold=500 -XX:-TieredCompilation Test
## 静态编译与动态编译
许多高级编程语言如 C 或 C++ 从一开始就使用 AOT 编译。它们也被称为**静态编译**语言。由于 AOT(或静态)编译器不受性能要求的约束(至少没有运行时的解释器(也称为**动态编译器**)那么多),因此它们可以花时间进行复杂的代码优化。另一方面,静态编译器没有运行时(评测)数据,这在动态类型语言的情况下尤其有限,Java 就是其中之一。由于 Java 中的动态类型功能(向下转换到子类型、查询对象的类型以及其他类型操作)是面向对象编程(多态原理)的支柱之一,因此 Java 的 AOT 编译变得更加有限。Lambda 表达式暂停了静态编译的另一个挑战,目前还不受支持。**
许多高级编程语言如 C 或 C++ 从一开始就使用 AOT 编译。它们也被称为**静态编译**语言。由于 AOT(或静态)编译器不受性能要求的约束(至少没有运行时的解释器(也称为**动态编译器**)那么多),因此它们可以花时间进行复杂的代码优化。另一方面,静态编译器没有运行时(评测)数据,这在动态类型语言的情况下尤其有限,Java 就是其中之一。由于 Java 中的动态类型功能(向下转换到子类型、查询对象的类型以及其他类型操作)是面向对象编程(多态原理)的支柱之一,因此 Java 的 AOT 编译变得更加有限。Lambda 表达式暂停了静态编译的另一个挑战,目前还不受支持。
动态编译器的另一个优点是,它可以做出假设并相应地优化代码。如果假设被证明是错误的,编译器可以尝试另一个假设,直到达到性能目标。这样的过程可能会减慢应用程序的速度和/或增加预热时间,但从长远来看,它可能会带来更好的性能。概要文件引导的优化也可以帮助静态编译器沿着这条路径前进,但与动态编译器相比,静态编译器的优化机会总是有限的。
......
......@@ -6,7 +6,7 @@
但在计算机数据处理的情况下,当我们听到工作线程同时执行时,我们会自动地假设它们实际上执行了它们被编程为并行执行的任务。只有在我们深入研究了这样一个系统之后,我们才意识到,只有当线程分别由不同的 CPU 执行时,这样的并行处理才是可能的。否则,它们的时间共享相同的处理能力,我们认为它们在同一时间工作只是因为它们使用的时间间隔非常短——只是我们在日常生活中使用的时间单位的一小部分。当线程共享同一资源时,在计算机科学中,我们说它们同时执行。
在本课中,我们将讨论通过使用并发处理数据的工作线程(worker)来提高 Java 应用程序性能的方法。我们将展示如何通过池化有效地使用线程,如何同步并发访问的数据,如何在运行时监视和调优工作线程,以及如何利用反应式编程概念。
在本课中,我们将讨论通过使用并发处理数据的工作线程(工作器)来提高 Java 应用程序性能的方法。我们将展示如何通过池化有效地使用线程,如何同步并发访问的数据,如何在运行时监视和调优工作线程,以及如何利用反应式编程概念。
但在此之前,让我们回顾一下在同一 Java 进程中创建和运行多个线程的基础知识。
......@@ -178,7 +178,7 @@ class MyRunnable01 implements Runnable {
}
```
它与前面的`Thread01`类具有相同的功能,并且我们添加了 id,允许在必要时识别线程,因为`Runnable`接口没有`Thread`类所具有的内置`getName()`方法。
它与前面的`Thread01`类具有相同的功能,并且我们添加了 ID,允许在必要时识别线程,因为`Runnable`接口没有`Thread`类所具有的内置`getName()`方法。
类似地,如果我们在不暂停`main`线程的情况下执行该类,如下所示:
......@@ -215,7 +215,7 @@ System.out.println("Worker " + myRunnable.getId()
![Prerequisites](img/3_07.jpg)
前面的所有示例都将生成的结果存储在 class 属性中。但情况并非总是如此。通常,工作线程要么将其值传递给另一个线程,要么将其存储在数据库或外部的其他地方。在这种情况下,可以利用`Runnable`接口作为功能接口,将必要的处理函数作为 lambda 表达式传递到新线程中:
前面的所有示例都将生成的结果存储在`class`属性中。但情况并非总是如此。通常,工作线程要么将其值传递给另一个线程,要么将其存储在数据库或外部的其他地方。在这种情况下,可以利用`Runnable`接口作为功能接口,将必要的处理函数作为 lambda 表达式传递到新线程中:
```java
System.out.print("demo_lambda_01(): ");
......@@ -501,7 +501,7 @@ class MyRunnable02 implements Runnable {
# 监控线程
有两种方法可以监视线程,通过编程和使用外部工具。我们已经看到如何检查工人计算的结果。让我们重温一下这段代码。我们还将稍微修改我们的 worker 实现:
有两种方法可以监视线程,通过编程和使用外部工具。我们已经看到如何检查工人计算的结果。让我们重温一下这段代码。我们还将稍微修改我们的工作器实现:
```java
class MyRunnable03 implements Runnable {
......@@ -847,10 +847,10 @@ void printThreadsInfo() {
如果您无法访问源代码或更喜欢使用外部工具进行线程监视,那么 JDK 安装中有几个诊断实用程序。在下面的列表中,我们仅提及允许线程监视的工具,并仅描述所列工具的此功能(尽管它们也具有其他广泛的功能):
* `jcmd`实用程序使用 JVM 进程 ID 或主类名称`jcmd <process id/main class> <command> [options]`向同一台机器上的 JVM 发送诊断命令请求,`Thread.print`选项打印进程中所有线程的堆栈跟踪。
* JConsole 监控工具使用 JVM 中内置的 JMX 工具来提供有关运行应用程序的性能和资源消耗的信息。它有一个“线程”选项卡窗格,其中显示了一段时间内的线程使用情况、当前的活动线程数、自 JVM 启动以来的最高活动线程数。可以选择线程及其名称、状态和堆栈跟踪,对于阻塞的线程,还可以选择线程等待获取的同步器以及拥有锁的线程。使用**死锁检测**按钮识别死锁。运行该工具的命令是`jconsole <process id>`或(对于远程应用程序)`jconsole <hostname>:<port>`,其中`port`是启用 JMX 代理的 JVM start 命令指定的端口号。
* JConsole 监控工具使用 JVM 中内置的 JMX 工具来提供有关运行应用程序的性能和资源消耗的信息。它有一个“线程”选项卡窗格,其中显示了一段时间内的线程使用情况、当前的活动线程数、自 JVM 启动以来的最高活动线程数。可以选择线程及其名称、状态和堆栈跟踪,对于阻塞的线程,还可以选择线程等待获取的同步器以及拥有锁的线程。使用**死锁检测**按钮识别死锁。运行该工具的命令是`jconsole <process id>`或(对于远程应用程序)`jconsole <hostname>:<port>`,其中`port`是启用 JMX 代理的 JVM `start`命令指定的端口号。
* `jdb`实用程序是一个示例命令行调试器。它可以附加到 JVM 进程,并允许您检查线程。
* `jstack`命令行实用程序可以附加到 JVM 进程,并打印所有线程的堆栈跟踪,包括 JVM 内部线程和可选的本机堆栈帧。它还允许您检测死锁。
* **Java 飞行记录器**(**JFR**)提供 Java 进程的相关信息,包括等待锁的线程、垃圾收集等。它还允许获取线程转储,类似于通过`Thread.print`诊断命令或使用 jstack 工具生成的线程转储。如果满足条件,可以设置**Java 任务控制**(**JMC**)以转储飞行记录。JMCUI 包含关于线程、锁争用和其他延迟的信息。尽管 JFR 是一项商业功能,但它对开发人员台式机/笔记本电脑以及测试、开发和生产环境中的评估目的是免费的。
* **Java 飞行记录器**(**JFR**)提供 Java 进程的相关信息,包括等待锁的线程、垃圾收集等。它还允许获取线程转储,类似于通过`Thread.print`诊断命令或使用 JStack 工具生成的线程转储。如果满足条件,可以设置**Java 任务控制**(**JMC**)以转储飞行记录。JMCUI 包含关于线程、锁争用和其他延迟的信息。尽管 JFR 是一项商业功能,但它对开发人员台式机/笔记本电脑以及测试、开发和生产环境中的评估目的是免费的。
### 注
......@@ -888,11 +888,11 @@ ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, T
* `maximumPoolSize`:这是池中允许的最大线程数
* `keepAliveTime`:当线程数大于核心数时,这是多余空闲线程等待新任务终止前的最长时间
* `unit`:这是`keepAliveTime`参数的时间单位
* `workQueue`:此队列用于在任务执行前保存任务,此队列将只保存 execute 方法提交的`Runnable`任务
* `workQueue`:此队列用于在任务执行前保存任务,此队列将只保存`execute`方法提交的`Runnable`任务
* `threadFactory`:这是执行器创建新线程时使用的工厂
* `handler`:这是由于达到线程边界和队列容量而导致执行受阻时要使用的处理程序
在`ThreadPoolExecutor`方法的对象创建完成后,除`workQueue`参数外,之前的每个构造函数参数也可以通过相应的 setter 进行设置,这样可以更灵活地动态调整现有池特性。
在`ThreadPoolExecutor`方法的对象创建完成后,除`workQueue`参数外,之前的每个构造函数参数也可以通过相应的设置器进行设置,这样可以更灵活地动态调整现有池特性。
# 线程同步
......@@ -1105,7 +1105,7 @@ Lock lock = ...;
应用程序分析可能表明,一个特定的操作会在循环中不断分配大量内存。如果您可以访问代码,您可以尝试优化代码的这一部分,从而减轻 JVM 的压力。或者,它可能表明存在一个 I/O 或涉及到另一个与低端设备的交互,并且在代码中您无法对其进行任何改进。
定义应用程序和 JVM 调优的目标需要建立度量。例如,众所周知,作为平均响应时间的延迟的传统度量方法隐藏的比它揭示的性能更多。更好的延迟指标是最大响应时间和 99%的最佳响应时间。对于吞吐量,一个好的指标是每单位时间的事务数。通常,此指标(每个事务的时间)的倒数反映了延迟。对于内存占用,最大分配内存(在负载下)允许硬件规划和设置防止可怕的`OutOfMemoryError`异常。避免完全(停止世界)垃圾收集循环将是理想的。但是,在实践中,如果**完全 GC**不经常发生,不会明显影响性能,并且在几个周期后得到大致相同的堆大小,这就足够了。
定义应用程序和 JVM 调优的目标需要建立度量。例如,众所周知,作为平均响应时间的延迟的传统度量方法隐藏的比它揭示的性能更多。更好的延迟指标是最大响应时间和 99% 的最佳响应时间。对于吞吐量,一个好的指标是每单位时间的事务数。通常,此指标(每个事务的时间)的倒数反映了延迟。对于内存占用,最大分配内存(在负载下)允许硬件规划和设置防止可怕的`OutOfMemoryError`异常。避免完全(停止世界)垃圾收集循环将是理想的。但是,在实践中,如果**完全 GC**不经常发生,不会明显影响性能,并且在几个周期后得到大致相同的堆大小,这就足够了。
不幸的是,这种需求的简单性在实践中确实存在。现实生活总是带来更多的问题,如下所示:
......@@ -1153,7 +1153,7 @@ Oracle 还建议使用 G1 及其默认设置,然后使用`-XX:MaxGCPauseMillis
在经历了几次错误的开始和几次灾难性的破坏之后,接着是英勇的恢复,金字塔的建造过程形成了,古代的建筑者能够完成一些项目。最终的形状有时看起来与预想的不完全一样(最初的金字塔最终弯曲了),但是,尽管如此,金字塔仍然装饰着今天的沙漠。这种经历代代相传,设计和工艺都经过了很好的调整,足以在 4000 多年后生产出一些华丽、宜人的东西。
软件实践也会随着时间的推移而改变,尽管自图灵先生编写第一个现代程序以来,我们只有大约 70 年的时间。起初,当世界上只有少数几个程序员时,计算机程序曾经是一个连续的指令列表。函数式编程(像一等公民一样推动函数)也很早就被引入,但尚未成为主流。相反,`goto`指令允许您在意大利面碗中滚动代码。随后是结构化编程,然后是面向对象编程,函数式编程正在发展,甚至在某些领域蓬勃发展。对按键生成的事件的异步处理成为许多程序员的例行程序。JavaScript 试图使用所有的最佳实践,并获得了很多功能,即使在调试(乐趣)阶段以程序员的挫折为代价。最后,由于线程池和 lambda 表达式是 jdkse 的一部分,将 reactivestreamsapi 添加到 jdk9 使 Java 成为允许使用异步数据流进行反应式编程的家族的一部分。
软件实践也会随着时间的推移而改变,尽管自图灵先生编写第一个现代程序以来,我们只有大约 70 年的时间。起初,当世界上只有少数几个程序员时,计算机程序曾经是一个连续的指令列表。函数式编程(像一等公民一样推动函数)也很早就被引入,但尚未成为主流。相反,`goto`指令允许您在意大利面碗中滚动代码。随后是结构化编程,然后是面向对象编程,函数式编程正在发展,甚至在某些领域蓬勃发展。对按键生成的事件的异步处理成为许多程序员的例行程序。JavaScript 试图使用所有的最佳实践,并获得了很多功能,即使在调试(乐趣)阶段以程序员的挫折为代价。最后,由于线程池和 lambda 表达式是 JDKSE 的一部分,将反应流 API 添加到 jdk9 使 Java 成为允许使用异步数据流进行反应式编程的家族的一部分。
公平地说,即使没有这个新 API,我们也能够异步处理数据——通过旋转工作线程、使用线程池和可调用项(如前几节所述),或者通过传递回调(即使有时在调用谁的人的迷宫中丢失一次)。但是,在编写了几次这样的代码之后,我们注意到,大多数这样的代码只是一个管道,可以包装在一个框架内,从而大大简化异步处理。[这就是反应流倡议的方式](http://www.reactive-streams.org),工作范围定义如下:
......@@ -1186,13 +1186,13 @@ public static interface Flow.Processor<T,R>
}
```
`Flow.Subscriber`对象在`Flow.Subscriber`对象作为参数传递到`subscribe()`方法后,成为`Flow.Publisher`对象生成的数据的订户。发布者(`Flow.Publisher`的对象)调用订阅者的`onSubscribe()`方法,并将`Flow.Subsctiption`对象作为参数传递。现在,订阅者可以通过调用订阅的`request()`方法向发布者请求`numberOffItems`数据。当订阅者决定何时请求另一项进行处理时,这就是实现 pull 模型的方法。订阅者可以通过调用`cancel()`订阅方式取消订阅发布者服务。
`Flow.Subscriber`对象在`Flow.Subscriber`对象作为参数传递到`subscribe()`方法后,成为`Flow.Publisher`对象生成的数据的订户。发布者(`Flow.Publisher`的对象)调用订阅者的`onSubscribe()`方法,并将`Flow.Subsctiption`对象作为参数传递。现在,订阅者可以通过调用订阅的`request()`方法向发布者请求`numberOffItems`数据。当订阅者决定何时请求另一项进行处理时,这就是实现拉取模型的方法。订阅者可以通过调用`cancel()`订阅方式取消订阅发布者服务。
作为回报(或者在没有任何请求的情况下,如果实现者已经决定这样做,这将是一个推送模型),发布者可以通过调用订阅者的`onNext()`方法将一个新项目传递给订阅者。发布者还可以告诉订阅者物品生产遇到了问题(通过调用订阅者的`onError()`方法)或将不会有更多数据(通过调用订阅者的`onComplete()`方法)。
`Flow.Processor`接口描述了一个既可以作为订阅者又可以作为发布者的实体。它允许创建此类处理器的链(管道),以便订阅者可以从发布者接收项目,调整它,然后将结果传递给下一个订阅者。
这是 Responsive Streams initiative 为支持具有非阻塞背压的异步数据流而定义的最小接口集(现在是 JDK 9 的一部分)。如您所见,它允许订阅者和发布者相互交谈,并在需要时协调传入数据的速率,从而为我们在开始时讨论的背压问题提供多种解决方案。
这是响应流倡议为支持具有非阻塞背压的异步数据流而定义的最小接口集(现在是 JDK 9 的一部分)。如您所见,它允许订阅者和发布者相互交谈,并在需要时协调传入数据的速率,从而为我们在开始时讨论的背压问题提供多种解决方案。
有许多方法可以实现这些接口。目前,在 JDK 9 中,只有一个接口的实现示例,`SubmissionPublisher`类实现了`Flow.Publisher`。但是已经存在其他几个实现反应流 API 的库:RxJava、Reactor、Akka Streams 和 Vert.x 是最有名的库。我们将在示例中使用 RXJava2.1.3。您可以在[这里](http://reactivex.io)找到 RxJava 2.x API名称为 ReactiveX,表示被动扩展。
......
......@@ -69,7 +69,7 @@ web 服务器和数据库集群在一定程度上有所帮助,因为随着代
* **Akka**:这是一个为 Java 和 Scala 构建高度并发、分布式和弹性消息驱动应用程序的工具包([Akka.io](https://akka.io/)
* **Bootique**:这是一个针对可运行 Java 应用程序的最低限度的自以为是的框架([Bootique.io](http://bootique.io)
* **Dropwizard**:这是一个用于开发 ops 友好、高性能、RESTful web 服务的 Java 框架([www.Dropwizard.io](http://www.dropwizard.io)
* **Dropwizard**:这是一个用于 DEVOPS 友好、高性能、RESTful web 服务的 Java 框架([www.Dropwizard.io](http://www.dropwizard.io)
* **Jodd**:这是一套 Java 微框架、工具和实用程序,低于 1.7MB([Jodd.org](http://jodd.org)
* **Lightbend-Lagom**:这是一个基于 Akka 和 Play 的固执己见的微服务框架([www.Lightbend.com](http://www.lightbend.com)
* **忍者**:这是一个针对 Java 的全栈 web 框架([www.ninjaframework.org](http://www.ninjaframework.org)
......@@ -92,7 +92,7 @@ public interface Verticle {
}
```
前面提到的方法名称是不言自明的。方法`getVertex()`提供对`Vertx`对象的访问,该对象是 Vert.x 核心 API 的入口点。它提供了对 microservices 大楼所需的以下功能的访问:
前面提到的方法名称是不言自明的。方法`getVertex()`提供对`Vertx`对象的访问,该对象是 Vert.x 核心 API 的入口点。它提供了对微服务大楼所需的以下功能的访问:
* 创建 TCP 和 HTTP 客户端和服务器
* 创建 DNS 客户端
......@@ -102,7 +102,7 @@ public interface Verticle {
* 提供对共享数据 API 的访问
* 部署和取消部署垂直线
使用这个 Vertx 对象,可以部署各种 Verticle,它们相互通信,接收外部请求,并像任何其他 Java 应用程序一样处理和存储数据,从而形成一个微服务系统。使用包`io.vertx.rxjava`中的 RxJava 实现,我们将展示如何创建一个反应式的微服务系统。
使用这个 Vertx 对象,可以部署各种`Verticle`,它们相互通信,接收外部请求,并像任何其他 Java 应用程序一样处理和存储数据,从而形成一个微服务系统。使用包`io.vertx.rxjava`中的 RxJava 实现,我们将展示如何创建一个反应式的微服务系统。
垂直体是垂直体中的构造块。`x`世界。可以通过扩展`io.vertx.rxjava.core.AbstractVerticle`类轻松创建:
......@@ -186,7 +186,7 @@ public abstract class AbstractVerticle
如您所见,重要的是不要编写阻塞事件循环的代码,因此 Vert.x 黄金法则:不要阻塞事件循环。
如果不阻塞,事件循环工作得非常快,并在短时间内交付大量事件。这称为[反应器模式](https://en.wikipedia.org/wiki/Reactor_pattern)。这种事件驱动的非阻塞编程模型非常适合反应式微服务。对于某些本质上是阻塞的代码类型(JDBC 调用和长时间计算是很好的例子),可以异步执行 worker verticle(不是通过事件循环,而是通过使用方法`vertx.executeBlocking()`的单独线程),这保持了黄金法则的完整性。
如果不阻塞,事件循环工作得非常快,并在短时间内交付大量事件。这称为[反应器模式](https://en.wikipedia.org/wiki/Reactor_pattern)。这种事件驱动的非阻塞编程模型非常适合反应式微服务。对于某些本质上是阻塞的代码类型(JDBC 调用和长时间计算是很好的例子),可以异步执行工作器`Verticle`(不是通过事件循环,而是通过使用方法`vertx.executeBlocking()`的单独线程),这保持了黄金法则的完整性。
让我们看几个例子。下面是一个用作 HTTP 服务器的`Verticle`类:
......@@ -397,7 +397,7 @@ public class DbHandler {
}
```
熟悉 RxJava 的人可以看到,Vert.x 代码非常遵循 RxJava 的样式和命名约定。尽管如此,我们还是鼓励您阅读 Vert.x 文档,因为它有一个非常丰富的 API,涵盖了比刚才演示的更多的情况。在前面的代码中,操作`flatMap()`接收运行脚本的函数,然后关闭连接。在这种情况下,操作`doAfterTerminate()`的作用就像它被放在传统代码中的 finally 块中一样,如果成功或生成异常,则关闭连接。`subscribe()`方法有几个重载版本。对于我们的代码,我们选择了一个包含两个函数的函数,一个在成功的情况下执行(我们打印一条关于正在创建的表的消息),另一个在异常的情况下执行(然后我们只打印堆栈跟踪)。
熟悉 RxJava 的人可以看到,Vert.x 代码非常遵循 RxJava 的样式和命名约定。尽管如此,我们还是鼓励您阅读 Vert.x 文档,因为它有一个非常丰富的 API,涵盖了比刚才演示的更多的情况。在前面的代码中,操作`flatMap()`接收运行脚本的函数,然后关闭连接。在这种情况下,操作`doAfterTerminate()`的作用就像它被放在传统代码中的`finally`块中一样,如果成功或生成异常,则关闭连接。`subscribe()`方法有几个重载版本。对于我们的代码,我们选择了一个包含两个函数的函数,一个在成功的情况下执行(我们打印一条关于正在创建的表的消息),另一个在异常的情况下执行(然后我们只打印堆栈跟踪)。
要使用创建的数据库,我们可以添加到`DbHandler`方法`insert()``process()``readProcessed()`,这将允许我们演示如何构建反应式系统。方法`insert()`的代码可以如下所示:
......@@ -593,7 +593,7 @@ Table who_called created
原则上,建立一个反应式系统已经足够了。我们可以在不同的端口上部署许多`DbServiceHttp`微服务,或将其群集,以提高处理能力、恢复能力和响应能力。我们可以将其他服务封装在 HTTP 客户机或 HTTP 服务器中,让它们相互通信,处理输入并沿处理管道传递结果。
然而,Vert.x 还有一个更适合消息驱动体系结构的特性(不使用 HTTP)。它被称为事件总线。任何 verticle 都可以访问事件总线,并且可以使用方法`send()``rxSend()`(在反应式编程的情况下)或方法`publish()`将任何消息发送到任何地址(只是一个字符串)。一个或多个垂直站点可以将自己注册为某个地址的消费者。
然而,Vert.x 还有一个更适合消息驱动体系结构的特性(不使用 HTTP)。它被称为事件总线。任何`Verticle`都可以访问事件总线,并且可以使用方法`send()``rxSend()`(在反应式编程的情况下)或方法`publish()`将任何消息发送到任何地址(只是一个字符串)。一个或多个垂直站点可以将自己注册为某个地址的消费者。
如果多个垂直站点是同一地址的消费者,则方法`send()``rxSend()`)只将消息传递给其中一个(使用循环算法选择下一个消费者)。正如您所期望的,方法`publish()`将消息传递给具有相同地址的所有消费者。让我们看一个例子,使用已经熟悉的`DbHandler`作为主要工作马。
......@@ -793,7 +793,7 @@ RxHelper.deployVerticle(vertx,
这是因为部署了两个使用者`DbServiceBus(1)``DbServiceBus(2)`,每个使用者都接收到一条发送到地址`INSERT`的消息,并将其插入表`who_called`中。
前面的所有示例都在一个 JVM 进程中运行。如有必要,可以在不同的 JVM 进程中部署 Vert.x 实例,并通过在 run 命令中添加`-cluster`选项进行集群。因此,它们共享事件总线,并且所有 Vert.x 实例都可以看到这些地址。这样,可以根据需要将资源添加到每个地址。例如,我们可以只增加处理微服务的数量,并补偿负载的增加。
前面的所有示例都在一个 JVM 进程中运行。如有必要,可以在不同的 JVM 进程中部署 Vert.x 实例,并通过在`run`命令中添加`-cluster`选项进行集群。因此,它们共享事件总线,并且所有 Vert.x 实例都可以看到这些地址。这样,可以根据需要将资源添加到每个地址。例如,我们可以只增加处理微服务的数量,并补偿负载的增加。
我们前面提到的其他框架也有类似的功能。它们使微服务的创建变得容易,并可能鼓励将应用程序分解为微小的单一方法操作,以期望组装一个非常有弹性和响应性的系统。
......@@ -815,7 +815,7 @@ RxHelper.deployVerticle(vertx,
这就是为什么后来人们开始谈论无容器部署时,他们通常指的是能够直接将应用程序部署到 JVM 中,而无需首先安装 WebSphere、WebLogic、JBoss 或为应用程序提供运行时环境的任何其他中介软件。
在前面的部分中,我们描述了许多框架,这些框架允许我们构建和部署应用程序(或者更确切地说是一个反应式的微服务系统),而不需要 JVM 本身之外的任何其他容器。您需要做的就是构建一个包含所有依赖项(来自 JVM 本身的依赖项除外)的 fat JAR 文件,然后将其作为独立的 Java 进程运行:
在前面的部分中,我们描述了许多框架,这些框架允许我们构建和部署应用程序(或者更确切地说是一个反应式的微服务系统),而不需要 JVM 本身之外的任何其他容器。您需要做的就是构建一个包含所有依赖项(来自 JVM 本身的依赖项除外)的 JAR 文件,然后将其作为独立的 Java 进程运行:
```java
$ java -jar myfatjar.jar
......@@ -845,7 +845,7 @@ $ java -jar myfatjar.jar
# 自给式微服务
自包含的微服务看起来非常类似于无容器服务。唯一的区别是,运行应用程序所需的 JVM(实际上是 JRE)或任何其他外部框架和服务器也包含在 fat JAR 文件中。有很多方法可以构建这样一个包罗万象的 JAR 文件。
自包含的微服务看起来非常类似于无容器服务。唯一的区别是,运行应用程序所需的 JVM(实际上是 JRE)或任何其他外部框架和服务器也包含在 JAR 文件中。有很多方法可以构建这样一个包罗万象的 JAR 文件。
例如,SpringBoot 提供了一个带有复选框列表的方便 GUI,允许您选择要打包的 SpringBoot 应用程序和外部工具的哪些部分。类似地,WildFly Swarm 允许您选择希望与应用程序捆绑在一起的 JavaEE 组件的哪些部分。或者,您可以使用`javapackager`工具自己完成。它将应用程序和 JRE 编译并打包在同一个 JAR 文件中(也可以是`.exe``.dmg`),以便分发。您可以在 [Oracle 网站](https://docs.oracle.com/javase/9/tools/javapackager.htm)上了解该工具或者您可以在安装了 JDK 的计算机上运行`javapackager`命令(它也随 Java 8 提供),您将获得工具选项列表及其简要说明。
......@@ -871,7 +871,7 @@ $ java -jar myfatjar.jar
关键区别在于,可以作为 VM 传递的捆绑包包括整个操作系统(部署了应用程序)。因此,运行两个虚拟机的物理服务器很可能运行两个不同的操作系统。相比之下,运行三个容器化应用程序的物理服务器(或 VM)只有一个运行的操作系统,两个容器共享(只读)操作系统内核,每个容器都有自己的访问(装载)来写入它们不共享的资源。例如,这意味着启动时间要短得多,因为启动容器不需要我们启动操作系统(如 VM)。
举个例子,让我们仔细看看集装箱码头的社区领袖。2015 年,一项名为**开放容器项目**的计划被宣布,后来更名为**开放容器计划****OCI**),该计划得到了谷歌、IBM、亚马逊、微软、红帽、甲骨文、VMware、惠普、推特等多家公司的支持。其目的是为所有平台开发容器格式和容器运行时软件的行业标准。Docker 已将其大约 5%的代码库捐赠给该项目,因为其解决方案被选为起点。
举个例子,让我们仔细看看集装箱码头的社区领袖。2015 年,一项名为**开放容器项目**的计划被宣布,后来更名为**开放容器计划****OCI**),该计划得到了谷歌、IBM、亚马逊、微软、红帽、甲骨文、VMware、惠普、推特等多家公司的支持。其目的是为所有平台开发容器格式和容器运行时软件的行业标准。Docker 已将其大约 5% 的代码库捐赠给该项目,因为其解决方案被选为起点。
[这里](https://docs.docker.com)有大量 Docker 文档。使用 Docker,可以将所有 JavaEE 容器和应用程序作为 Docker 映像包含在包中,实现与自包含部署基本相同的结果。然后,您可以使用以下命令在 Docker 引擎中启动 Docker 映像来启动应用程序:
......@@ -902,7 +902,7 @@ $ docker run mygreatapplication
# 总结
Microservices 是一种针对高负载处理系统的新架构和设计解决方案,在亚马逊、谷歌、推特、微软、IBM 等巨头成功地将其应用于生产后,该解决方案变得非常流行。但这并不意味着你也必须采用它,但是你可以考虑新的方法,看看它中的一些或任何能帮助你的应用程序更具弹性和反应性。
微服务是一种针对高负载处理系统的新架构和设计解决方案,在亚马逊、谷歌、推特、微软、IBM 等巨头成功地将其应用于生产后,该解决方案变得非常流行。但这并不意味着你也必须采用它,但是你可以考虑新的方法,看看它中的一些或任何能帮助你的应用程序更具弹性和反应性。
使用微服务可以提供巨大的价值,但不是免费的。从需求、开发到测试再到生产,在整个生命周期中管理更多单元的需求变得越来越复杂。在致力于全面的微服务体系结构之前,先尝试一下,只实现几个微服务,然后将它们全部转移到生产环境中。然后,让它运行一段时间,评估体验。这将是非常具体的您的组织。任何成功的解决方案都不能盲目复制,而应根据您的特殊需要和能力加以采用。
......@@ -912,7 +912,7 @@ Microservices 是一种针对高负载处理系统的新架构和设计解决方
# 评估
1. 通过使用 _________;对象,可以部署各种垂直站点,它们相互通信,接收外部请求,并像任何其他 Java 应用程序一样处理和存储数据,从而形成一个微服务系统。
1. 通过使用 _________ 对象,可以部署各种垂直站点,它们相互通信,接收外部请求,并像任何其他 Java 应用程序一样处理和存储数据,从而形成一个微服务系统。
2. 以下哪项是无容器部署的优势?
1. 每个 JAR 文件都需要某个版本或更高版本的 JVM,这可能会迫使您为此启动一个新的物理或虚拟机,部署一个特定的 JAR 文件
......
......@@ -1235,7 +1235,7 @@ public static void main(String... arg) {
激发`Optional`类创建的愿景是对`Optional`对象调用`isPresent()`方法,然后仅当`isPresent()`方法返回`true`时才应用`get()`方法(获取包含的值)。不幸的是,当无法保证对`Optional`对象本身的引用不是`null`时,需要检查它以避免`NullPointerException`。如果是这样的话,那么使用`Optional`的价值就会降低,因为通过更少的代码编写,我们可以检查`null`值本身,避免将其包装在`Optional`中?让我们编写代码来说明我们一直在讨论的内容。
假设我们想写一个检查彩票结果的方法,如果你和朋友一起买的彩票中奖了,计算你 50%的份额。传统的做法是:
假设我们想写一个检查彩票结果的方法,如果你和朋友一起买的彩票中奖了,计算你 50% 的份额。传统的做法是:
```java
void checkResultInt(int lotteryPrize){
......
......@@ -942,7 +942,7 @@ java --add-modules ALL-MODULE-PATH --module-path ../6_bottom_up_migration_after/
![](img/a82c6458-5c5b-4270-a8a5-308a39b9eb12.png)
我们无法对`calculator.jar`进行模块化,因为它依赖于另一个非模块化代码`jackson-databind`,我们无法对`jackson-databind`进行模块化,因为它不是由我们维护的。这意味着我们无法实现应用程序的 100%模块化。在本配方的开头,我们向您介绍了未命名的模块。我们在类路径中的所有非模块代码都分组在未命名的模块中,这意味着所有与 jackson 相关的代码仍然可以保留在未命名的模块中,我们可以尝试模块化`calculator.jar`。但是我们不能这样做,因为`calculator.jar`不能声明对`jackson-databind-2.8.4.jar`的依赖关系(因为它是一个未命名的模块,命名的模块不能声明对未命名模块的依赖关系)。
我们无法对`calculator.jar`进行模块化,因为它依赖于另一个非模块化代码`jackson-databind`,我们无法对`jackson-databind`进行模块化,因为它不是由我们维护的。这意味着我们无法实现应用程序的 100% 模块化。在本配方的开头,我们向您介绍了未命名的模块。我们在类路径中的所有非模块代码都分组在未命名的模块中,这意味着所有与 jackson 相关的代码仍然可以保留在未命名的模块中,我们可以尝试模块化`calculator.jar`。但是我们不能这样做,因为`calculator.jar`不能声明对`jackson-databind-2.8.4.jar`的依赖关系(因为它是一个未命名的模块,命名的模块不能声明对未命名模块的依赖关系)。
解决这个问题的一种方法是将与 jackson 相关的代码作为自动模块。我们可以通过移动与 jackson 相关的JAR来实现这一点:
......
......@@ -920,7 +920,7 @@ Heroku 提供了一个名为 Heroku cli([cli.Heroku.com](http://cli.heroku.com
在虚拟化中,通过在多个虚拟机之间分配计算能力、内存和存储,将大型服务器划分为多个虚拟机。通过这种方式,每个虚拟机本身都能够实现服务器的所有功能,尽管规模较小。通过这种虚拟化,我们可以明智地利用服务器的计算、内存和存储资源。
但是,虚拟化需要一些设置,也就是说,您需要创建虚拟机,安装所需的依赖项,然后运行应用程序。此外,您可能无法 100%确定应用程序是否会成功运行。失败的原因可能是由于操作系统版本不兼容,甚至可能是由于在设置时遗漏了某些配置或缺少某些依赖项。这种设置还会导致横向扩展方面的一些困难,因为在调配虚拟机然后部署应用程序时会花费一些时间。
但是,虚拟化需要一些设置,也就是说,您需要创建虚拟机,安装所需的依赖项,然后运行应用程序。此外,您可能无法 100% 确定应用程序是否会成功运行。失败的原因可能是由于操作系统版本不兼容,甚至可能是由于在设置时遗漏了某些配置或缺少某些依赖项。这种设置还会导致横向扩展方面的一些困难,因为在调配虚拟机然后部署应用程序时会花费一些时间。
使用 Puppet 和 Chef 等工具确实有助于进行资源调配,但设置应用程序通常会导致由于配置缺失或不正确而导致的问题。这导致了另一个概念的引入,称为集装箱化。
......
......@@ -35,7 +35,7 @@ GC 堆和堆栈使用两个内存区域。第一个用于 JVM 分配内存和存
# 了解 G1 垃圾收集器
先前的 GC 实现包括**串行 GC****并行 GC****并发标记扫描****CMS**)收集器。他们将堆分为三个部分:年轻一代、老年代或终身一代,以及巨大的区域,用于容纳标准区域大小 50%或更大的对象。年轻一代包含大多数新创建的对象;这是最具活力的区域,因为大多数对象都是短期的,并且很快(随着年龄的增长)就有资格收集。期限是指对象存活下来的收集周期数。年轻一代有三个收集周期——一个*伊甸空间*和两个幸存者空间,如幸存者 0(`S0`)和幸存者 1(`S1`)。对象会在其中移动(根据其年龄和其他一些特征),直到最终被丢弃或放置在老年代中。
先前的 GC 实现包括**串行 GC****并行 GC****并发标记扫描****CMS**)收集器。他们将堆分为三个部分:年轻一代、老年代或终身一代,以及巨大的区域,用于容纳标准区域大小 50% 或更大的对象。年轻一代包含大多数新创建的对象;这是最具活力的区域,因为大多数对象都是短期的,并且很快(随着年龄的增长)就有资格收集。期限是指对象存活下来的收集周期数。年轻一代有三个收集周期——一个*伊甸空间*和两个幸存者空间,如幸存者 0(`S0`)和幸存者 1(`S1`)。对象会在其中移动(根据其年龄和其他一些特征),直到最终被丢弃或放置在老年代中。
老年代包含的对象比某个年龄段的对象旧。这个区域比年轻一代大,因此,这里的垃圾收集成本更高,而且不像年轻一代那样频繁。
......@@ -59,7 +59,7 @@ g1gc 的做法有些不同。它将堆划分为大小相等的区域,并为每
并行 GC 在所有可用的内核上并行工作,尽管可以配置线程的数量。它还可以阻止世界,并且只适用于能够承受长时间冻结的应用程序。
CMS 收集器旨在解决长暂停问题。它这样做的代价是不对老年代进行碎片整理,并在应用程序执行的同时进行一些分析(通常使用 25%的 CPU)。老年代的收集在其已满 68%时开始(默认情况下,但可以配置此值)。
CMS 收集器旨在解决长暂停问题。它这样做的代价是不对老年代进行碎片整理,并在应用程序执行的同时进行一些分析(通常使用 25% 的 CPU)。老年代的收集在其已满 68% 时开始(默认情况下,但可以配置此值)。
G1 GC 算法类似于 CMS 收集器。首先,它同时标识堆中所有被引用的对象并相应地标记它们。然后它首先收集最空的区域,从而释放大量的可用空间。这就是为什么它被称为*垃圾优先*。因为它使用了许多小的专用区域,所以它有更好的机会预测清理其中一个区域所需的时间量,并拟合用户定义的暂停时间(G1 可能偶尔会超过它,但大多数时间都非常接近)。
......@@ -162,7 +162,7 @@ G1 GC 从年轻一代集合开始,使用停止世界进行疏散(将年轻
* `-XX:ParallelGCThreads=<ergo>`:保存垃圾收集暂停期间用于并行工作的最大线程数(默认情况下,从可用线程数派生;如果进程可用的 CPU 线程数小于或等于 8,则使用此数字;否则,将在最终线程数中添加大于 8 的线程的八分之五)
* `-XX:ConcGCThreads=<ergo>`:保存用于并发工作的最大线程数(默认设置为`-XX:ParallelGCThreads`除以四)。
* `-XX:+G1UseAdaptiveIHOP`:表示起始堆占用应该是自适应的
* `-XX:InitiatingHeapOccupancyPercent=45`:设置前几个采集周期;G1 将使用老年代的 45%的占有率作为标记开始阈值
* `-XX:InitiatingHeapOccupancyPercent=45`:设置前几个采集周期;G1 将使用老年代的 45% 的占有率作为标记开始阈值
* `-XX:G1HeapRegionSize=<ergo>`:根据初始堆大小和最大堆大小保存堆区域大小(默认情况下,因为堆包含大约 2048 个堆区域,所以堆区域的大小可以从 1 到 32 MB 不等,并且必须是 2 的幂)
* `-XX:G1NewSizePercent=5``-XX:XX:G1MaxNewSizePercent=60`:定义年轻一代的总大小,在这两个值之间变化为当前使用的 JVM 堆的百分比
* `-XX:G1HeapWastePercent=5`:以百分比形式保存候选集合集中允许的未回收空间(如果候选集合中的可用空间低于该百分比,G1 停止空间回收)
......@@ -1328,7 +1328,7 @@ String s2 = "this string takes as much memory as another one";
* `-Xms size`:此选项允许我们设置初始堆大小(必须大于 1MB,并且是 1024 的倍数)。
* `-Xmx size`:此选项允许我们设置最大堆大小(必须大于 2MB 和 1024 的倍数)。
* `-Xmn size``-XX:NewSize=size``-XX:MaxNewSize=size`的组合:此选项允许我们设置年轻一代的初始和最大大小。对于高效 GC,它必须低于`-Xmx size`。Oracle 建议将其设置为堆大小的 25%以上,50%以下。
* `-Xmn size``-XX:NewSize=size``-XX:MaxNewSize=size`的组合:此选项允许我们设置年轻一代的初始和最大大小。对于高效 GC,它必须低于`-Xmx size`。Oracle 建议将其设置为堆大小的 25% 以上,50% 以下。
* `-XX:NewRatio=ratio`:此选项允许我们设置年轻一代和老年代之间的比率(默认为两代)。
* `-Xss size`:此选项允许我们设置线程堆栈大小。以下是不同平台的默认值:
* Linux/ARM(32 位):320 KB
......@@ -1345,7 +1345,7 @@ String s2 = "this string takes as much memory as another one";
* `-XX:+HeapDumpOnOutOfMemoryError`:允许我们将 JVM 堆内容保存到文件中,但仅当抛出`java.lang.OutOfMemoryError`异常时。默认情况下,堆转储以`java_pid<pid>.hprof`名称保存在当前目录中,其中`<pid>`是进程 ID。使用`-XX:HeapDumpPath=<path>`选项自定义转储文件位置。`<path>`值必须包含文件名。
* `-XX:OnOutOfMemoryError="<cmd args>;<cmd args>"`:允许我们提供一组命令(用分号分隔),当抛出`OutOfMemoryError`异常时将执行这些命令。
* `-XX:+UseGCOverheadLimit`:调节 GC 在抛出`OutOfMemoryError`异常之前所花费的时间比例大小。例如,当 GC 占用 98%以上的时间并且恢复不到堆的 2%时,并行 GC 将抛出一个`OutOfMemoryError`异常。当堆很小时,此选项特别有用,因为它阻止 JVM 以很少或没有进展的方式运行。默认情况下,它处于启用状态。要禁用它,请使用`-XX:-UseGCOverheadLimit`
* `-XX:+UseGCOverheadLimit`:调节 GC 在抛出`OutOfMemoryError`异常之前所花费的时间比例大小。例如,当 GC 占用 98% 以上的时间并且恢复不到堆的 2% 时,并行 GC 将抛出一个`OutOfMemoryError`异常。当堆很小时,此选项特别有用,因为它阻止 JVM 以很少或没有进展的方式运行。默认情况下,它处于启用状态。要禁用它,请使用`-XX:-UseGCOverheadLimit`
# 了解低开销垃圾收集器 Epsilon
......
......@@ -347,7 +347,7 @@ public class CalculateSpeedSteps {
# 使用 JUnit 对 API 进行单元测试
据维基百科称,GitHub 上托管的项目中有 30%以上包括 JUnit,JUnit 是由 SUnit 发起的单元测试框架家族(统称为 xUnit)之一。它在编译时作为 JAR 链接,并驻留在`org.junit`包中(从 JUnit4 开始)。
据维基百科称,GitHub 上托管的项目中有 30% 以上包括 JUnit,JUnit 是由 SUnit 发起的单元测试框架家族(统称为 xUnit)之一。它在编译时作为 JAR 链接,并驻留在`org.junit`包中(从 JUnit4 开始)。
在面向对象编程中,单元可以是整个类,但也可以是单个方法。我们发现最后一部分——单元作为单独的方法在实践中最有用。它是本章食谱示例的基础。
......
......@@ -1850,7 +1850,7 @@ public class DockerTest {
[安卓](https://www.android.com/)是基于 Linux 修改版的开源移动操作系统。它最初由一家名为 Android 的初创公司开发,2005 年被谷歌收购并支持。
根据 Gartner Inc.(美国 IT 研究和咨询公司)的报告,2017 年,Android 和 iOS 占全球智能手机销量的 99%以上,如下图所示:
根据 Gartner Inc.(美国 IT 研究和咨询公司)的报告,2017 年,Android 和 iOS 占全球智能手机销量的 99% 以上,如下图所示:
![](img/00127.jpeg)
......
......@@ -114,7 +114,7 @@
# 测试覆盖率
测试覆盖率是 SUT 中为其任何测试执行的代码的比率。测试覆盖率对于查找 SUT 的未测试部分非常有用。因此,它可以是完美的白盒技术(结构)来补充黑盒(功能)。一般来说,80%或以上的测试覆盖率是合理的。
测试覆盖率是 SUT 中为其任何测试执行的代码的比率。测试覆盖率对于查找 SUT 的未测试部分非常有用。因此,它可以是完美的白盒技术(结构)来补充黑盒(功能)。一般来说,80% 或以上的测试覆盖率是合理的。
有不同的 Java 库,允许以简单的方式进行测试覆盖,例如:
......
......@@ -685,7 +685,7 @@ apply plugin: 'jacoco'
$ gradle clean test jacocoTestReport
```
最终结果是位于`build/reports/jacoco/test/html`目录中的报告。根据您为本练习制定的解决方案,结果会有所不同。我的结果是有 100%的指令覆盖率和 96%的分支覆盖率;缺少 4%,因为没有玩家在 0 或负值的方框上玩的测试用例。这种情况的实现是存在的,但是没有具体的测试覆盖它。总的来说,这是一个相当不错的报道:
最终结果是位于`build/reports/jacoco/test/html`目录中的报告。根据您为本练习制定的解决方案,结果会有所不同。我的结果是有 100% 的指令覆盖率和 96% 的分支覆盖率;缺少 4%,因为没有玩家在 0 或负值的方框上玩的测试用例。这种情况的实现是存在的,但是没有具体的测试覆盖它。总的来说,这是一个相当不错的报道:
![](img/ad410f3d-0ec5-4109-a35a-0e9ca579d7d7.png)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册