From 7cf404ba5943c07dac36b7f698e973ab904223a5 Mon Sep 17 00:00:00 2001 From: Guide Date: Wed, 18 Sep 2024 14:29:31 +0800 Subject: [PATCH] =?UTF-8?q?[docs=20update]=E5=86=85=E5=AE=B9=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network/other-network-questions.md | 2 +- docs/database/redis/redis-questions-01.md | 11 +++-- docs/java/basis/java-basic-questions-02.md | 48 +++++++++++++++++-- docs/java/basis/java-basic-questions-03.md | 4 +- .../concurrent/completablefuture-intro.md | 10 +++- 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/docs/cs-basics/network/other-network-questions.md b/docs/cs-basics/network/other-network-questions.md index 91b5f4d1982..bce91e25099 100644 --- a/docs/cs-basics/network/other-network-questions.md +++ b/docs/cs-basics/network/other-network-questions.md @@ -215,7 +215,7 @@ HTTP/2.0 多路复用效果图(图源: [HTTP/2 For Web Developers](https://b - **队头阻塞**:HTTP/2.0 多请求复用一个 TCP 连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。由于 QUIC 协议的特性,HTTP/3.0 在一定程度上解决了队头阻塞(Head-of-Line blocking, 简写:HOL blocking)问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)。 - **连接迁移**:HTTP/3.0 支持连接迁移,因为 QUIC 使用 64 位 ID 标识连接,只要 ID 不变就不会中断,网络环境改变时(如从 Wi-Fi 切换到移动数据)也能保持连接。而 TCP 连接是由(源 IP,源端口,目的 IP,目的端口)组成,这个四元组中一旦有一项值发生改变,这个连接也就不能用了。 - **错误恢复**:HTTP/3.0 具有更好的错误恢复机制,当出现丢包、延迟等网络问题时,可以更快地进行恢复和重传。而 HTTP/2.0 则需要依赖于 TCP 的错误恢复机制。 -- **安全性**:在 HTTP/2.0 中,TLS 用于加密和认证整个 HTTP 会话,包括所有的 HTTP 头部和数据负载。TLS的工作是在 TCP 层之上,它加密的是在 TCP 连接中传输的应用层的数据,并不会对 TCP 头部以及 TLS 记录层头部进行加密,所以在传输的过程中 TCP 头部可能会被攻击者篡改来干扰通信。而 HTTP/3.0 的 QUIC 对整个数据包(包括报文头和报文体)进行了加密与认证处理,保障安全性。 +- **安全性**:在 HTTP/2.0 中,TLS 用于加密和认证整个 HTTP 会话,包括所有的 HTTP 头部和数据负载。TLS 的工作是在 TCP 层之上,它加密的是在 TCP 连接中传输的应用层的数据,并不会对 TCP 头部以及 TLS 记录层头部进行加密,所以在传输的过程中 TCP 头部可能会被攻击者篡改来干扰通信。而 HTTP/3.0 的 QUIC 对整个数据包(包括报文头和报文体)进行了加密与认证处理,保障安全性。 HTTP/1.0、HTTP/2.0 和 HTTP/3.0 的协议栈比较: diff --git a/docs/database/redis/redis-questions-01.md b/docs/database/redis/redis-questions-01.md index 75da270252b..3261d7a17c7 100644 --- a/docs/database/redis/redis-questions-01.md +++ b/docs/database/redis/redis-questions-01.md @@ -780,10 +780,15 @@ dynamic-hz yes ### 大量 key 集中过期怎么办? -如果存在大量 key 集中过期的问题,可能会使 Redis 的请求延迟变高。可以采用下面的可选方案来应对: +当 Redis 中存在大量 key 在同一时间点集中过期时,可能会导致以下问题: -1. 尽量避免 key 集中过期,在设置键的过期时间时尽量随机一点。 -2. 对过期的 key 开启 lazyfree 机制(修改 `redis.conf` 中的 `lazyfree-lazy-expire`参数即可),这样会在后台异步删除过期的 key,不会阻塞主线程的运行。 +- **请求延迟增加:** Redis 在处理过期 key 时需要消耗 CPU 资源,如果过期 key 数量庞大,会导致 Redis 实例的 CPU 占用率升高,进而影响其他请求的处理速度,造成延迟增加。 +- **内存占用过高:** 过期的 key 虽然已经失效,但在 Redis 真正删除它们之前,仍然会占用内存空间。如果过期 key 没有及时清理,可能会导致内存占用过高,甚至引发内存溢出。 + +为了避免这些问题,可以采取以下方案: + +1. **尽量避免 key 集中过期**: 在设置键的过期时间时尽量随机一点。 +2. **开启 lazy free 机制**: 修改 `redis.conf` 配置文件,将 `lazyfree-lazy-expire` 参数设置为 `yes`,即可开启 lazy free 机制。开启 lazy free 机制后,Redis 会在后台异步删除过期的 key,不会阻塞主线程的运行,从而降低对 Redis 性能的影响。 ### Redis 内存淘汰策略了解么? diff --git a/docs/java/basis/java-basic-questions-02.md b/docs/java/basis/java-basic-questions-02.md index cf7362a2207..78fa48add0f 100644 --- a/docs/java/basis/java-basic-questions-02.md +++ b/docs/java/basis/java-basic-questions-02.md @@ -706,9 +706,14 @@ System.out.println(aa==bb); // true ### String s1 = new String("abc");这句话创建了几个字符串对象? -会创建 1 或 2 个字符串对象。 +先说答案:会创建 1 或 2 个字符串对象。 -1、如果字符串常量池中不存在字符串对象 “abc”,那么它首先会在字符串常量池中创建字符串对象 "abc",然后在堆内存中再创建其中一个字符串对象 "abc" +1. 字符串常量池中不存在 "abc":会创建 2 个 字符串对象。一个在字符串常量池中,由 `ldc` 指令触发创建。一个在堆中,由 `new String()` 创建,并使用常量池中的 "abc" 进行初始化。 +2. 字符串常量池中已存在 "abc":会创建 1 个 字符串对象。该对象在堆中,由 `new String()` 创建,并使用常量池中的 "abc" 进行初始化。 + +下面开始详细分析。 + +1、如果字符串常量池中不存在字符串对象 “abc”,那么它首先会在字符串常量池中创建字符串对象 "abc",然后在堆内存中再创建其中一个字符串对象 "abc"。 示例代码(JDK 1.8): @@ -718,9 +723,33 @@ String s1 = new String("abc"); 对应的字节码: -![](https://oss.javaguide.cn/github/javaguide/open-source-project/image-20220413175809959.png) +```java +// 在堆内存中分配一个尚未初始化的 String 对象。 +// #2 是常量池中的一个符号引用,指向 java/lang/String 类。 +// 在类加载的解析阶段,这个符号引用会被解析成直接引用,即指向实际的 java/lang/String 类。 +0 new #2 +// 复制栈顶的 String 对象引用,为后续的构造函数调用做准备。 +// 此时操作数栈中有两个相同的对象引用:一个用于传递给构造函数,另一个用于保持对新对象的引用,后续将其存储到局部变量表。 +3 dup +// JVM 先检查字符串常量池中是否存在 "abc"。 +// 如果常量池中已存在 "abc",则直接返回该字符串的引用; +// 如果常量池中不存在 "abc",则 JVM 会在常量池中创建该字符串字面量并返回它的引用。 +// 这个引用被压入操作数栈,用作构造函数的参数。 +4 ldc #3 +// 调用构造方法,使用从常量池中加载的 "abc" 初始化堆中的 String 对象 +// 新的 String 对象将包含与常量池中的 "abc" 相同的内容,但它是一个独立的对象,存储于堆中。 +6 invokespecial #4 : (Ljava/lang/String;)V> +// 将堆中的 String 对象引用存储到局部变量表 +9 astore_1 +// 返回,结束方法 +10 return +``` -`ldc (load constant)` 指令的作用是从常量池中加载常量,包括字符串常量、整数常量、浮点数常量、或者类引用。这里用于判断字符串常量池中是否保存了对应的字符串对象,如果保存了的话会将它的引用加载到操作数栈,如果没有保存的话,会在字符串常量池中创建对应的字符串对象,并将其引用加载到操作数栈中。 +`ldc (load constant)` 指令的确是从常量池中加载各种类型的常量,包括字符串常量、整数常量、浮点数常量,甚至类引用等。对于字符串常量,`ldc` 指令的行为如下: + +1. **从常量池加载字符串**:`ldc` 首先检查字符串常量池中是否已经有内容相同的字符串对象。 +2. **复用已有字符串对象**:如果字符串常量池中已经存在内容相同的字符串对象,`ldc` 会将该对象的引用加载到操作数栈上。 +3. **没有则创建新对象并加入常量池**:如果字符串常量池中没有相同内容的字符串对象,JVM 会在常量池中创建一个新的字符串对象,并将其引用加载到操作数栈中。 2、如果字符串常量池中已存在字符串对象“abc”,则只会在堆中创建 1 个字符串对象“abc”。 @@ -735,7 +764,16 @@ String s2 = new String("abc"); 对应的字节码: -![](https://oss.javaguide.cn/github/javaguide/open-source-project/image-20220413180021072.png) +```java +0 ldc #2 +2 astore_1 +3 new #3 +6 dup +7 ldc #2 +9 invokespecial #4 : (Ljava/lang/String;)V> +12 astore_2 +13 return +``` 这里就不对上面的字节码进行详细注释了,7 这个位置的 `ldc` 命令不会在堆中创建新的字符串对象“abc”,这是因为 0 这个位置已经执行了一次 `ldc` 命令,已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 `ldc` 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。 diff --git a/docs/java/basis/java-basic-questions-03.md b/docs/java/basis/java-basic-questions-03.md index 8d64ab99461..00fd999b5a1 100644 --- a/docs/java/basis/java-basic-questions-03.md +++ b/docs/java/basis/java-basic-questions-03.md @@ -55,8 +55,8 @@ head: ### Throwable 类常用方法有哪些? -- `String getMessage()`: 返回异常发生时的简要描述 -- `String toString()`: 返回异常发生时的详细信息 +- `String getMessage()`: 返回异常发生时的详细信息 +- `String toString()`: 返回异常发生时的简要描述 - `String getLocalizedMessage()`: 返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同 - `void printStackTrace()`: 在控制台上打印 `Throwable` 对象封装的异常信息 diff --git a/docs/java/concurrent/completablefuture-intro.md b/docs/java/concurrent/completablefuture-intro.md index 6e130c98419..b0a0cf987e6 100644 --- a/docs/java/concurrent/completablefuture-intro.md +++ b/docs/java/concurrent/completablefuture-intro.md @@ -653,7 +653,15 @@ abc 我们上面的代码示例中,为了方便,都没有选择自定义线程池。实际项目中,这是不可取的。 -`CompletableFuture` 默认使用`ForkJoinPool.commonPool()` 作为执行器,这个线程池是全局共享的,可能会被其他任务占用,导致性能下降或者饥饿。因此,建议使用自定义的线程池来执行 `CompletableFuture` 的异步任务,可以提高并发度和灵活性。 +`CompletableFuture` 默认使用全局共享的 `ForkJoinPool.commonPool()` 作为执行器,所有未指定执行器的异步任务都会使用该线程池。这意味着应用程序、多个库或框架(如 Spring、第三方库)若都依赖 `CompletableFuture`,默认情况下它们都会共享同一个线程池。 + +虽然 `ForkJoinPool` 效率很高,但当同时提交大量任务时,可能会导致资源竞争和线程饥饿,进而影响系统性能。 + +为避免这些问题,建议为 `CompletableFuture` 提供自定义线程池,带来以下优势: + +- **隔离性**:为不同任务分配独立的线程池,避免全局线程池资源争夺。 +- **资源控制**:根据任务特性调整线程池大小和队列类型,优化性能表现。 +- **异常处理**:通过自定义 `ThreadFactory` 更好地处理线程中的异常情况。 ```java private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10,