mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
Compare commits
8 Commits
ff77b0cabf
...
2e7aa2a8eb
Author | SHA1 | Date | |
---|---|---|---|
|
2e7aa2a8eb | ||
|
153c81c2f6 | ||
|
1fc64fca0c | ||
|
abd12cab1d | ||
|
3b1767d6e3 | ||
|
1eee19f991 | ||
|
9f164247f6 | ||
|
d578883043 |
@ -11,31 +11,61 @@ tag:
|
||||
|
||||
### TCP 与 UDP 的区别(重要)
|
||||
|
||||
1. **是否面向连接**:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
|
||||
2. **是否是可靠传输**:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
|
||||
3. **是否有状态**:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(**这很渣男!**)。
|
||||
4. **传输效率**:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
|
||||
5. **传输形式**:TCP 是面向字节流的,UDP 是面向报文的。
|
||||
6. **首部开销**:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
|
||||
7. **是否提供广播或多播服务**:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;
|
||||
1. **是否面向连接**:
|
||||
- TCP 是面向连接的。在传输数据之前,必须先通过“三次握手”建立连接;数据传输完成后,还需要通过“四次挥手”来释放连接。这保证了双方都准备好通信。
|
||||
- UDP 是无连接的。发送数据前不需要建立任何连接,直接把数据包(数据报)扔出去。
|
||||
2. **是否是可靠传输**:
|
||||
- TCP 提供可靠的数据传输服务。它通过序列号、确认应答 (ACK)、超时重传、流量控制、拥塞控制等一系列机制,来确保数据能够无差错、不丢失、不重复且按顺序地到达目的地。
|
||||
- UDP 提供不可靠的传输。它尽最大努力交付 (best-effort delivery),但不保证数据一定能到达,也不保证到达的顺序,更不会自动重传。收到报文后,接收方也不会主动发确认。
|
||||
3. **是否有状态**:
|
||||
- TCP 是有状态的。因为要保证可靠性,TCP 需要在连接的两端维护连接状态信息,比如序列号、窗口大小、哪些数据发出去了、哪些收到了确认等。
|
||||
- UDP 是无状态的。它不维护连接状态,发送方发出数据后就不再关心它是否到达以及如何到达,因此开销更小(**这很“渣男”!**)。
|
||||
4. **传输效率**:
|
||||
- TCP 因为需要建立连接、发送确认、处理重传等,其开销较大,传输效率相对较低。
|
||||
- UDP 结构简单,没有复杂的控制机制,开销小,传输效率更高,速度更快。
|
||||
5. **传输形式**:
|
||||
- TCP 是面向字节流 (Byte Stream) 的。它将应用程序交付的数据视为一连串无结构的字节流,可能会对数据进行拆分或合并。
|
||||
- UDP 是面向报文 (Message Oriented) 的。应用程序交给 UDP 多大的数据块,UDP 就照样发送,既不拆分也不合并,保留了应用程序消息的边界。
|
||||
6. **首部开销**:
|
||||
- TCP 的头部至少需要 20 字节,如果包含选项字段,最多可达 60 字节。
|
||||
- UDP 的头部非常简单,固定只有 8 字节。
|
||||
7. **是否提供广播或多播服务**:
|
||||
- TCP 只支持点对点 (Point-to-Point) 的单播通信。
|
||||
- UDP 支持一对一 (单播)、一对多 (多播/Multicast) 和一对所有 (广播/Broadcast) 的通信方式。
|
||||
8. ……
|
||||
|
||||
我把上面总结的内容通过表格形式展示出来了!确定不点个赞嘛?
|
||||
为了更直观地对比,可以看下面这个表格:
|
||||
|
||||
| | TCP | UDP |
|
||||
| ---------------------- | -------------- | ---------- |
|
||||
| 是否面向连接 | 是 | 否 |
|
||||
| 是否可靠 | 是 | 否 |
|
||||
| 是否有状态 | 是 | 否 |
|
||||
| 传输效率 | 较慢 | 较快 |
|
||||
| 传输形式 | 字节流 | 数据报文段 |
|
||||
| 首部开销 | 20 ~ 60 bytes | 8 bytes |
|
||||
| 是否提供广播或多播服务 | 否 | 是 |
|
||||
| 特性 | TCP | UDP |
|
||||
| ------------ | -------------------------- | ----------------------------------- |
|
||||
| **连接性** | 面向连接 | 无连接 |
|
||||
| **可靠性** | 可靠 | 不可靠 (尽力而为) |
|
||||
| **状态维护** | 有状态 | 无状态 |
|
||||
| **传输效率** | 较低 | 较高 |
|
||||
| **传输形式** | 面向字节流 | 面向数据报 (报文) |
|
||||
| **头部开销** | 20 - 60 字节 | 8 字节 |
|
||||
| **通信模式** | 点对点 (单播) | 单播、多播、广播 |
|
||||
| **常见应用** | HTTP/HTTPS, FTP, SMTP, SSH | DNS, DHCP, SNMP, TFTP, VoIP, 视频流 |
|
||||
|
||||
### 什么时候选择 TCP,什么时候选 UDP?
|
||||
|
||||
- **UDP 一般用于即时通信**,比如:语音、 视频、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。
|
||||
- **TCP 用于对传输准确性要求特别高的场景**,比如文件传输、发送和接收邮件、远程登录等等。
|
||||
选择 TCP 还是 UDP,主要取决于你的应用**对数据传输的可靠性要求有多高,以及对实时性和效率的要求有多高**。
|
||||
|
||||
当**数据准确性和完整性至关重要,一点都不能出错**时,通常选择 TCP。因为 TCP 提供了一整套机制(三次握手、确认应答、重传、流量控制等)来保证数据能够可靠、有序地送达。典型应用场景如下:
|
||||
|
||||
- **Web 浏览 (HTTP/HTTPS):** 网页内容、图片、脚本必须完整加载才能正确显示。
|
||||
- **文件传输 (FTP, SCP):** 文件内容不允许有任何字节丢失或错序。
|
||||
- **邮件收发 (SMTP, POP3, IMAP):** 邮件内容需要完整无误地送达。
|
||||
- **远程登录 (SSH, Telnet):** 命令和响应需要准确传输。
|
||||
- ......
|
||||
|
||||
当**实时性、速度和效率优先,并且应用能容忍少量数据丢失或乱序**时,通常选择 UDP。UDP 开销小、传输快,没有建立连接和保证可靠性的复杂过程。典型应用场景如下:
|
||||
|
||||
- **实时音视频通信 (VoIP, 视频会议, 直播):** 偶尔丢失一两个数据包(可能导致画面或声音短暂卡顿)通常比因为等待重传(TCP 机制)导致长时间延迟更可接受。应用层可能会有自己的补偿机制。
|
||||
- **在线游戏:** 需要快速传输玩家位置、状态等信息,对实时性要求极高,旧的数据很快就没用了,丢失少量数据影响通常不大。
|
||||
- **DHCP (动态主机配置协议):** 客户端在请求 IP 时自身没有 IP 地址,无法满足 TCP 建立连接的前提条件,并且 DHCP 有广播需求、交互模式简单以及自带可靠性机制。
|
||||
- **物联网 (IoT) 数据上报:** 某些场景下,传感器定期上报数据,丢失个别数据点可能不影响整体趋势分析。
|
||||
- ......
|
||||
|
||||
### HTTP 基于 TCP 还是 UDP?
|
||||
|
||||
|
@ -553,31 +553,26 @@ MVCC 在 MySQL 中实现所依赖的手段主要是: **隐藏字段、read view
|
||||
|
||||
### SQL 标准定义了哪些事务隔离级别?
|
||||
|
||||
SQL 标准定义了四个隔离级别:
|
||||
SQL 标准定义了四种事务隔离级别,用来平衡事务的隔离性(Isolation)和并发性能。级别越高,数据一致性越好,但并发性能可能越低。这四个级别是:
|
||||
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。这种级别在实际应用中很少使用,因为它对数据一致性的保证太弱。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库(如 Oracle, SQL Server)的默认隔离级别。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 存储引擎的默认隔离级别正是 REPEATABLE READ。并且,InnoDB 在此级别下通过 MVCC(多版本并发控制) 和 Next-Key Locks(间隙锁+行锁) 机制,在很大程度上解决了幻读问题。
|
||||
- **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
|
||||
|
||||
---
|
||||
|
||||
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|
||||
| :--------------: | :--: | :--------: | :--: |
|
||||
| READ-UNCOMMITTED | √ | √ | √ |
|
||||
| READ-COMMITTED | × | √ | √ |
|
||||
| REPEATABLE-READ | × | × | √ |
|
||||
| SERIALIZABLE | × | × | × |
|
||||
|
||||
### MySQL 的隔离级别是基于锁实现的吗?
|
||||
|
||||
MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
|
||||
|
||||
SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
|
||||
| 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) |
|
||||
| ---------------- | ----------------- | -------------------------------- | ---------------------- |
|
||||
| READ UNCOMMITTED | √ | √ | √ |
|
||||
| READ COMMITTED | × | √ | √ |
|
||||
| REPEATABLE READ | × | × | √ (标准) / ≈× (InnoDB) |
|
||||
| SERIALIZABLE | × | × | × |
|
||||
|
||||
### MySQL 的默认隔离级别是什么?
|
||||
|
||||
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
|
||||
MySQL InnoDB 存储引擎的默认隔离级别是 **REPEATABLE READ**。可以通过以下命令查看:
|
||||
|
||||
- MySQL 8.0 之前:`SELECT @@tx_isolation;`
|
||||
- MySQL 8.0 及之后:`SELECT @@transaction_isolation;`
|
||||
|
||||
```sql
|
||||
mysql> SELECT @@tx_isolation;
|
||||
@ -590,6 +585,12 @@ mysql> SELECT @@tx_isolation;
|
||||
|
||||
关于 MySQL 事务隔离级别的详细介绍,可以看看我写的这篇文章:[MySQL 事务隔离级别详解](./transaction-isolation-level.md)。
|
||||
|
||||
### MySQL 的隔离级别是基于锁实现的吗?
|
||||
|
||||
MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
|
||||
|
||||
SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
|
||||
|
||||
## MySQL 锁
|
||||
|
||||
锁是一种常见的并发事务的控制方式。
|
||||
|
@ -11,43 +11,46 @@ tag:
|
||||
|
||||
## 事务隔离级别总结
|
||||
|
||||
SQL 标准定义了四个隔离级别:
|
||||
SQL 标准定义了四种事务隔离级别,用来平衡事务的隔离性(Isolation)和并发性能。级别越高,数据一致性越好,但并发性能可能越低。这四个级别是:
|
||||
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。这种级别在实际应用中很少使用,因为它对数据一致性的保证太弱。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库(如 Oracle, SQL Server)的默认隔离级别。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 存储引擎的默认隔离级别正是 REPEATABLE READ。并且,InnoDB 在此级别下通过 MVCC(多版本并发控制) 和 Next-Key Locks(间隙锁+行锁) 机制,在很大程度上解决了幻读问题。
|
||||
- **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
|
||||
|
||||
---
|
||||
| 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) |
|
||||
| ---------------- | ----------------- | -------------------------------- | ---------------------- |
|
||||
| READ UNCOMMITTED | √ | √ | √ |
|
||||
| READ COMMITTED | × | √ | √ |
|
||||
| REPEATABLE READ | × | × | √ (标准) / ≈× (InnoDB) |
|
||||
| SERIALIZABLE | × | × | × |
|
||||
|
||||
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|
||||
| :--------------: | :--: | :--------: | :--: |
|
||||
| READ-UNCOMMITTED | √ | √ | √ |
|
||||
| READ-COMMITTED | × | √ | √ |
|
||||
| REPEATABLE-READ | × | × | √ |
|
||||
| SERIALIZABLE | × | × | × |
|
||||
**默认级别查询:**
|
||||
|
||||
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
|
||||
MySQL InnoDB 存储引擎的默认隔离级别是 **REPEATABLE READ**。可以通过以下命令查看:
|
||||
|
||||
```sql
|
||||
MySQL> SELECT @@tx_isolation;
|
||||
+-----------------+
|
||||
| @@tx_isolation |
|
||||
+-----------------+
|
||||
| REPEATABLE-READ |
|
||||
+-----------------+
|
||||
- MySQL 8.0 之前:`SELECT @@tx_isolation;`
|
||||
- MySQL 8.0 及之后:`SELECT @@transaction_isolation;`
|
||||
|
||||
```bash
|
||||
mysql> SELECT @@transaction_isolation;
|
||||
+-------------------------+
|
||||
| @@transaction_isolation |
|
||||
+-------------------------+
|
||||
| REPEATABLE-READ |
|
||||
+-------------------------+
|
||||
```
|
||||
|
||||
从上面对 SQL 标准定义了四个隔离级别的介绍可以看出,标准的 SQL 隔离级别定义里,REPEATABLE-READ(可重复读)是不可以防止幻读的。
|
||||
**InnoDB 的 REPEATABLE READ 对幻读的处理:**
|
||||
|
||||
但是!InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况:
|
||||
标准的 SQL 隔离级别定义里,REPEATABLE READ 是无法防止幻读的。但 InnoDB 的实现通过以下机制很大程度上避免了幻读:
|
||||
|
||||
- **快照读**:由 MVCC 机制来保证不出现幻读。
|
||||
- **当前读**:使用 Next-Key Lock 进行加锁来保证不出现幻读,Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的结合,行锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。
|
||||
- **快照读 (Snapshot Read)**:普通的 SELECT 语句,通过 **MVCC** 机制实现。事务启动时创建一个数据快照,后续的快照读都读取这个版本的数据,从而避免了看到其他事务新插入的行(幻读)或修改的行(不可重复读)。
|
||||
- **当前读 (Current Read)**:像 `SELECT ... FOR UPDATE`, `SELECT ... LOCK IN SHARE MODE`, `INSERT`, `UPDATE`, `DELETE` 这些操作。InnoDB 使用 **Next-Key Lock** 来锁定扫描到的索引记录及其间的范围(间隙),防止其他事务在这个范围内插入新的记录,从而避免幻读。Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的组合。
|
||||
|
||||
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ** 并不会有任何性能损失。
|
||||
值得注意的是,虽然通常认为隔离级别越高、并发性越差,但 InnoDB 存储引擎通过 MVCC 机制优化了 REPEATABLE READ 级别。对于许多常见的只读或读多写少的场景,其性能**与 READ COMMITTED 相比可能没有显著差异**。不过,在写密集型且并发冲突较高的场景下,RR 的间隙锁机制可能会比 RC 带来更多的锁等待。
|
||||
|
||||
InnoDB 存储引擎在分布式事务的情况下一般会用到 SERIALIZABLE 隔离级别。
|
||||
此外,在某些特定场景下,如需要严格一致性的分布式事务(XA Transactions),InnoDB 可能要求或推荐使用 SERIALIZABLE 隔离级别来确保全局数据的一致性。
|
||||
|
||||
《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章这样写到:
|
||||
|
||||
|
@ -99,20 +99,20 @@ public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMod
|
||||
|
||||
```java
|
||||
public enum RoundingMode {
|
||||
// 2.5 -> 3 , 1.6 -> 2
|
||||
// -1.6 -> -2 , -2.5 -> -3
|
||||
// 2.4 -> 3 , 1.6 -> 2
|
||||
// -1.6 -> -2 , -2.4 -> -3
|
||||
UP(BigDecimal.ROUND_UP),
|
||||
// 2.5 -> 2 , 1.6 -> 1
|
||||
// -1.6 -> -1 , -2.5 -> -2
|
||||
// 2.4 -> 2 , 1.6 -> 1
|
||||
// -1.6 -> -1 , -2.4 -> -2
|
||||
DOWN(BigDecimal.ROUND_DOWN),
|
||||
// 2.5 -> 3 , 1.6 -> 2
|
||||
// -1.6 -> -1 , -2.5 -> -2
|
||||
// 2.4 -> 3 , 1.6 -> 2
|
||||
// -1.6 -> -1 , -2.4 -> -2
|
||||
CEILING(BigDecimal.ROUND_CEILING),
|
||||
// 2.5 -> 2 , 1.6 -> 1
|
||||
// -1.6 -> -2 , -2.5 -> -3
|
||||
FLOOR(BigDecimal.ROUND_FLOOR),
|
||||
// 2.5 -> 3 , 1.6 -> 2
|
||||
// -1.6 -> -2 , -2.5 -> -3
|
||||
// 2.4 -> 2 , 1.6 -> 2
|
||||
// -1.6 -> -2 , -2.4 -> -2
|
||||
HALF_UP(BigDecimal.ROUND_HALF_UP),
|
||||
//......
|
||||
}
|
||||
|
@ -83,7 +83,11 @@ public class RpcRequest implements Serializable {
|
||||
|
||||
~~`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 `static` 变量是属于类的而不是对象。你反序列之后,`static` 变量的值就像是默认赋予给了对象一样,看着就像是 `static` 变量被序列化,实际只是假象罢了。~~
|
||||
|
||||
**🐛 修正(参见:[issue#2174](https://github.com/Snailclimb/JavaGuide/issues/2174))**:`static` 修饰的变量是静态变量,属于类而非类的实例,本身是不会被序列化的。然而,`serialVersionUID` 是一个特例,`serialVersionUID` 的序列化做了特殊处理。当一个对象被序列化时,`serialVersionUID` 会被写入到序列化的二进制流中;在反序列化时,也会解析它并做一致性判断,以此来验证序列化对象的版本一致性。如果两者不匹配,反序列化过程将抛出 `InvalidClassException`,因为这通常意味着序列化的类的定义已经发生了更改,可能不再兼容。
|
||||
**🐛 修正(参见:[issue#2174](https://github.com/Snailclimb/JavaGuide/issues/2174))**:
|
||||
|
||||
通常情况下,`static` 变量是属于类的,不属于任何单个对象实例,所以它们本身不会被包含在对象序列化的数据流里。序列化保存的是对象的状态(也就是实例变量的值)。然而,`serialVersionUID` 是一个特例,`serialVersionUID` 的序列化做了特殊处理。关键在于,`serialVersionUID` 不是作为对象状态的一部分被序列化的,而是被序列化机制本身用作一个特殊的“指纹”或“版本号”。
|
||||
|
||||
当一个对象被序列化时,`serialVersionUID` 会被写入到序列化的二进制流中(像是在保存一个版本号,而不是保存 `static` 变量本身的状态);在反序列化时,也会解析它并做一致性判断,以此来验证序列化对象的版本一致性。如果两者不匹配,反序列化过程将抛出 `InvalidClassException`,因为这通常意味着序列化的类的定义已经发生了更改,可能不再兼容。
|
||||
|
||||
官方说明如下:
|
||||
|
||||
@ -91,7 +95,7 @@ public class RpcRequest implements Serializable {
|
||||
>
|
||||
> 如果想显式指定 `serialVersionUID` ,则需要在类中使用 `static` 和 `final` 关键字来修饰一个 `long` 类型的变量,变量名字必须为 `"serialVersionUID"` 。
|
||||
|
||||
也就是说,`serialVersionUID` 只是用来被 JVM 识别,实际并没有被序列化。
|
||||
也就是说,`serialVersionUID` 本身(作为 static 变量)确实不作为对象状态被序列化。但是,它的值被 Java 序列化机制特殊处理了——作为一个版本标识符被读取并写入序列化流中,用于在反序列化时进行版本兼容性检查。
|
||||
|
||||
**如果有些字段不想进行序列化怎么办?**
|
||||
|
||||
|
@ -403,7 +403,7 @@ Process finished with exit code 0
|
||||
|
||||
我们分析一下上面的代码为什么避免了死锁的发生?
|
||||
|
||||
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
|
||||
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了循环等待条件,因此避免了死锁。
|
||||
|
||||
## 虚拟线程
|
||||
|
||||
|
@ -8,77 +8,76 @@ tag:
|
||||
> 本文由 JavaGuide 翻译自 [https://www.baeldung.com/jvm-parameters](https://www.baeldung.com/jvm-parameters),并对文章进行了大量的完善补充。
|
||||
> 文档参数 [https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html)
|
||||
>
|
||||
> JDK 版本:1.8
|
||||
> JDK 版本:1.8 为主,也会补充新版本常用参数
|
||||
|
||||
## 1.概述
|
||||
在本篇文章中,我们将一起掌握 Java 虚拟机(JVM)中最常用的一些参数配置,帮助你更好地理解和调优 Java 应用的运行环境。
|
||||
|
||||
在本篇文章中,你将掌握最常用的 JVM 参数配置。
|
||||
## 堆内存相关
|
||||
|
||||
## 2.堆内存相关
|
||||
|
||||
> Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
|
||||
> Java 堆(Java Heap)是 JVM 所管理的内存中最大的一块区域,**所有线程共享**,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在堆上分配内存。**
|
||||
|
||||

|
||||
|
||||
### 2.1.显式指定堆内存`–Xms`和`-Xmx`
|
||||
### 设置堆内存大小 (-Xms 和 -Xmx)
|
||||
|
||||
与性能有关的最常见实践之一是根据应用程序要求初始化堆内存。如果我们需要指定最小和最大堆大小(推荐显示指定大小),以下参数可以帮助你实现:
|
||||
根据应用程序的实际需求设置初始和最大堆内存大小,是性能调优中最常见的实践之一。**推荐显式设置这两个参数,并且通常建议将它们设置为相同的值**,以避免运行时堆内存的动态调整带来的性能开销。
|
||||
|
||||
使用以下参数进行设置:
|
||||
|
||||
```bash
|
||||
-Xms<heap size>[unit]
|
||||
-Xmx<heap size>[unit]
|
||||
-Xms<heap size>[unit] # 设置 JVM 初始堆大小
|
||||
-Xmx<heap size>[unit] # 设置 JVM 最大堆大小
|
||||
```
|
||||
|
||||
- **heap size** 表示要初始化内存的具体大小。
|
||||
- **unit** 表示要初始化内存的单位。单位为 **_“ g”_** (GB)、**_“ m”_**(MB)、**_“ k”_**(KB)。
|
||||
- `<heap size>`: 指定内存的具体数值。
|
||||
- `[unit]`: 指定内存的单位,如 g (GB)、m (MB)、k (KB)。
|
||||
|
||||
举个栗子 🌰,如果我们要为 JVM 分配最小 2 GB 和最大 5 GB 的堆内存大小,我们的参数应该这样来写:
|
||||
**示例:** 将 JVM 的初始堆和最大堆都设置为 4GB:
|
||||
|
||||
```bash
|
||||
-Xms2G -Xmx5G
|
||||
-Xms4G -Xmx4G
|
||||
```
|
||||
|
||||
### 2.2.显式新生代内存(Young Generation)
|
||||
### 设置新生代内存大小 (Young Generation)
|
||||
|
||||
根据[Oracle 官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/sizing.html),在堆总可用内存配置完成之后,第二大影响因素是为 `Young Generation` 在堆内存所占的比例。默认情况下,YG 的最小大小为 1310 _MB_,最大大小为 _无限制_。
|
||||
根据[Oracle 官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/sizing.html),在堆总可用内存配置完成之后,第二大影响因素是为 `Young Generation` 在堆内存所占的比例。默认情况下,YG 的最小大小为 **1310 MB**,最大大小为 **无限制**。
|
||||
|
||||
一共有两种指定 新生代内存(Young Generation)大小的方法:
|
||||
可以通过以下两种方式设置新生代内存大小:
|
||||
|
||||
**1.通过`-XX:NewSize`和`-XX:MaxNewSize`指定**
|
||||
|
||||
```bash
|
||||
-XX:NewSize=<young size>[unit]
|
||||
-XX:MaxNewSize=<young size>[unit]
|
||||
-XX:NewSize=<young size>[unit] # 设置新生代初始大小
|
||||
-XX:MaxNewSize=<young size>[unit] # 设置新生代最大大小
|
||||
```
|
||||
|
||||
举个栗子 🌰,如果我们要为 新生代分配 最小 256m 的内存,最大 1024m 的内存我们的参数应该这样来写:
|
||||
**示例:** 设置新生代最小 512MB,最大 1024MB:
|
||||
|
||||
```bash
|
||||
-XX:NewSize=256m
|
||||
-XX:MaxNewSize=1024m
|
||||
-XX:NewSize=512m -XX:MaxNewSize=1024m
|
||||
```
|
||||
|
||||
**2.通过`-Xmn<young size>[unit]`指定**
|
||||
|
||||
举个栗子 🌰,如果我们要为 新生代分配 256m 的内存(NewSize 与 MaxNewSize 设为一致),我们的参数应该这样来写:
|
||||
**示例:** 将新生代大小固定为 512MB:
|
||||
|
||||
```bash
|
||||
-Xmn256m
|
||||
-Xmn512m
|
||||
```
|
||||
|
||||
GC 调优策略中很重要的一条经验总结是这样说的:
|
||||
|
||||
> 将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
|
||||
> 尽量让新创建的对象在新生代分配内存并被回收,因为 Minor GC 的成本通常远低于 Full GC。通过分析 GC 日志,判断新生代空间分配是否合理。如果大量新对象过早进入老年代(Promotion),可以适当通过 `-Xmn` 或 -`XX:NewSize/-XX:MaxNewSize` 调整新生代大小,目标是最大限度地减少对象直接进入老年代的情况。
|
||||
|
||||
另外,你还可以通过 **`-XX:NewRatio=<int>`** 来设置老年代与新生代内存的比值。
|
||||
另外,你还可以通过 **`-XX:NewRatio=<int>`** 参数来设置**老年代与新生代(不含 Survivor 区)的内存大小比例**。
|
||||
|
||||
比如下面的参数就是设置新生代与老年代内存的比值为 2(默认值)。也就是说 young/old 所占比值为 2,新生代占整个堆栈的 2/3。
|
||||
例如,`-XX:NewRatio=2` (默认值)表示老年代 : 新生代 = 2 : 1。即新生代占整个堆大小的 1/3。
|
||||
|
||||
```plain
|
||||
```bash
|
||||
-XX:NewRatio=2
|
||||
```
|
||||
|
||||
### 2.3.显式指定永久代/元空间的大小
|
||||
### 设置永久代/元空间大小 (PermGen/Metaspace)
|
||||
|
||||
**从 Java 8 开始,如果我们没有指定 Metaspace 的大小,随着更多类的创建,虚拟机会耗尽所有可用的系统内存(永久代并不会出现这种情况)。**
|
||||
|
||||
@ -102,7 +101,7 @@ JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参
|
||||
|
||||
**🐛 修正(参见:[issue#1947](https://github.com/Snailclimb/JavaGuide/issues/1947))**:
|
||||
|
||||
1、Metaspace 的初始容量并不是 `-XX:MetaspaceSize` 设置,无论 `-XX:MetaspaceSize` 配置什么值,对于 64 位 JVM 来说,Metaspace 的初始容量都是 21807104(约 20.8m)。
|
||||
**1、`-XX:MetaspaceSize` 并非初始容量:** Metaspace 的初始容量并不是 `-XX:MetaspaceSize` 设置,无论 `-XX:MetaspaceSize` 配置什么值,对于 64 位 JVM,元空间的初始容量通常是一个固定的较小值(Oracle 文档提到约 12MB 到 20MB 之间,实际观察约 20.8MB)。
|
||||
|
||||
可以参考 Oracle 官方文档 [Other Considerations](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html) 中提到的:
|
||||
|
||||
@ -112,11 +111,7 @@ JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参
|
||||
|
||||
另外,还可以看一下这个试验:[JVM 参数 MetaspaceSize 的误解](https://mp.weixin.qq.com/s/jqfppqqd98DfAJHZhFbmxA)。
|
||||
|
||||
2、Metaspace 由于使用不断扩容到`-XX:MetaspaceSize`参数指定的量,就会发生 FGC,且之后每次 Metaspace 扩容都会发生 Full GC。
|
||||
|
||||
也就是说,MetaspaceSize 表示 Metaspace 使用过程中触发 Full GC 的阈值,只对触发起作用。
|
||||
|
||||
垃圾搜集器内部是根据变量 `_capacity_until_GC`来判断 Metaspace 区域是否达到阈值的,初始化代码如下所示:
|
||||
**2、扩容与 Full GC:** 当 Metaspace 的使用量增长并首次达到`-XX:MetaspaceSize` 指定的阈值时,会触发一次 Full GC。在此之后,JVM 会动态调整这个触发 GC 的阈值。如果元空间继续增长,每次达到新的阈值需要扩容时,仍然可能触发 Full GC(具体行为与垃圾收集器和版本有关)。垃圾搜集器内部是根据变量 `_capacity_until_GC`来判断 Metaspace 区域是否达到阈值的,初始化代码如下所示:
|
||||
|
||||
```c
|
||||
void MetaspaceGC::initialize() {
|
||||
@ -126,111 +121,120 @@ void MetaspaceGC::initialize() {
|
||||
}
|
||||
```
|
||||
|
||||
**3、`-XX:MaxMetaspaceSize` 的重要性:**如果不显式设置 -`XX:MaxMetaspaceSize`,元空间的最大大小理论上受限于可用的本地内存。在极端情况下(如类加载器泄漏导致不断加载类),这确实**可能耗尽大量本地内存**。因此,**强烈建议设置一个合理的 `-XX:MaxMetaspaceSize` 上限**,以防止对系统造成影响。
|
||||
|
||||
相关阅读:[issue 更正:MaxMetaspaceSize 如果不指定大小的话,不会耗尽内存 #1204](https://github.com/Snailclimb/JavaGuide/issues/1204) 。
|
||||
|
||||
## 3.垃圾收集相关
|
||||
## 垃圾收集相关
|
||||
|
||||
### 3.1.垃圾回收器
|
||||
### 选择垃圾回收器
|
||||
|
||||
为了提高应用程序的稳定性,选择正确的[垃圾收集](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html)算法至关重要。
|
||||
选择合适的垃圾收集器(Garbage Collector, GC)对于应用的吞吐量和响应延迟至关重要。关于垃圾收集算法和收集器的详细介绍,可以看笔者写的这篇:[JVM 垃圾回收详解(重点)](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)。
|
||||
|
||||
JVM 具有四种类型的 GC 实现:
|
||||
JVM 提供了多种 GC 实现,适用于不同的场景:
|
||||
|
||||
- 串行垃圾收集器
|
||||
- 并行垃圾收集器
|
||||
- CMS 垃圾收集器
|
||||
- G1 垃圾收集器
|
||||
- **Serial GC (串行垃圾收集器):** 单线程执行 GC,适用于客户端模式或单核 CPU 环境。参数:`-XX:+UseSerialGC`。
|
||||
- **Parallel GC (并行垃圾收集器):** 多线程执行新生代 GC (Minor GC),以及可选的多线程执行老年代 GC (Full GC,通过 `-XX:+UseParallelOldGC`)。关注吞吐量,是 JDK 8 的默认 GC。参数:`-XX:+UseParallelGC`。
|
||||
- **CMS GC (Concurrent Mark Sweep 并发标记清除收集器):** 以获取最短回收停顿时间为目标,大部分 GC 阶段可与用户线程并发执行。适用于对响应时间要求高的应用。在 JDK 9 中被标记为弃用,JDK 14 中被移除。参数:`-XX:+UseConcMarkSweepGC`。
|
||||
- **G1 GC (Garbage-First Garbage Collector):** JDK 9 及之后版本的默认 GC。将堆划分为多个 Region,兼顾吞吐量和停顿时间,试图在可预测的停顿时间内完成 GC。参数:`-XX:+UseG1GC`。
|
||||
- **ZGC:** 更新的低延迟 GC,目标是将 GC 停顿时间控制在几毫秒甚至亚毫秒级别,需要较新版本的 JDK 支持。参数(具体参数可能随版本变化):`-XX:+UseZGC`、`-XX:+UseShenandoahGC`。
|
||||
|
||||
可以使用以下参数声明这些实现:
|
||||
### GC 日志记录
|
||||
|
||||
在生产环境或进行 GC 问题排查时,**务必开启 GC 日志记录**。详细的 GC 日志是分析和解决 GC 问题的关键依据。
|
||||
|
||||
以下是一些推荐配置的 GC 日志参数(适用于 JDK 8/11 等常见版本):
|
||||
|
||||
```bash
|
||||
-XX:+UseSerialGC
|
||||
-XX:+UseParallelGC
|
||||
-XX:+UseConcMarkSweepGC
|
||||
-XX:+UseG1GC
|
||||
```
|
||||
|
||||
有关 _垃圾回收_ 实施的更多详细信息,请参见[此处](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md)。
|
||||
|
||||
### 3.2.GC 日志记录
|
||||
|
||||
生产环境上,或者其他要测试 GC 问题的环境上,一定会配置上打印 GC 日志的参数,便于分析 GC 相关的问题。
|
||||
|
||||
```bash
|
||||
# 必选
|
||||
# 打印基本 GC 信息
|
||||
# --- 推荐的基础配置 ---
|
||||
# 打印详细 GC 信息
|
||||
-XX:+PrintGCDetails
|
||||
# 打印 GC 发生的时间戳 (相对于 JVM 启动时间)
|
||||
# -XX:+PrintGCTimeStamps
|
||||
# 打印 GC 发生的日期和时间 (更常用)
|
||||
-XX:+PrintGCDateStamps
|
||||
# 打印对象分布
|
||||
# 指定 GC 日志文件的输出路径,%t 可以输出日期时间戳
|
||||
-Xloggc:/path/to/gc-%t.log
|
||||
|
||||
# --- 推荐的进阶配置 ---
|
||||
# 打印对象年龄分布 (有助于判断对象晋升老年代的情况)
|
||||
-XX:+PrintTenuringDistribution
|
||||
# 打印堆数据
|
||||
# 在 GC 前后打印堆信息
|
||||
-XX:+PrintHeapAtGC
|
||||
# 打印Reference处理信息
|
||||
# 强引用/弱引用/软引用/虚引用/finalize 相关的方法
|
||||
# 打印各种类型引用 (强/软/弱/虚) 的处理信息
|
||||
-XX:+PrintReferenceGC
|
||||
# 打印STW时间
|
||||
# 打印应用暂停时间 (Stop-The-World, STW)
|
||||
-XX:+PrintGCApplicationStoppedTime
|
||||
|
||||
# 可选
|
||||
# 打印safepoint信息,进入 STW 阶段之前,需要要找到一个合适的 safepoint
|
||||
-XX:+PrintSafepointStatistics
|
||||
-XX:PrintSafepointStatisticsCount=1
|
||||
|
||||
# GC日志输出的文件路径
|
||||
-Xloggc:/path/to/gc-%t.log
|
||||
# 开启日志文件分割
|
||||
# --- GC 日志文件滚动配置 ---
|
||||
# 启用 GC 日志文件滚动
|
||||
-XX:+UseGCLogFileRotation
|
||||
# 最多分割几个文件,超过之后从头文件开始写
|
||||
# 设置滚动日志文件的数量 (例如,保留最近 14 个)
|
||||
-XX:NumberOfGCLogFiles=14
|
||||
# 每个文件上限大小,超过就触发分割
|
||||
# 设置每个日志文件的最大大小 (例如,50MB)
|
||||
-XX:GCLogFileSize=50M
|
||||
|
||||
# --- 可选的辅助诊断配置 ---
|
||||
# 打印安全点 (Safepoint) 统计信息 (有助于分析 STW 原因)
|
||||
# -XX:+PrintSafepointStatistics
|
||||
# -XX:PrintSafepointStatisticsCount=1
|
||||
```
|
||||
|
||||
## 4.处理 OOM
|
||||
**注意:** JDK 9 及之后版本引入了统一的 JVM 日志框架 (`-Xlog`),配置方式有所不同,但上述 `-Xloggc` 和滚动参数通常仍然兼容或有对应的新参数。
|
||||
|
||||
## 处理 OOM
|
||||
|
||||
对于大型应用程序来说,面对内存不足错误是非常常见的,这反过来会导致应用程序崩溃。这是一个非常关键的场景,很难通过复制来解决这个问题。
|
||||
|
||||
这就是为什么 JVM 提供了一些参数,这些参数将堆内存转储到一个物理文件中,以后可以用来查找泄漏:
|
||||
|
||||
```bash
|
||||
# 在发生 OOM 时生成堆转储文件
|
||||
-XX:+HeapDumpOnOutOfMemoryError
|
||||
-XX:HeapDumpPath=./java_pid<pid>.hprof
|
||||
-XX:OnOutOfMemoryError="< cmd args >;< cmd args >"
|
||||
|
||||
# 指定堆转储文件的输出路径。<pid> 会被替换为进程 ID
|
||||
-XX:HeapDumpPath=/path/to/heapdump/java_pid<pid>.hprof
|
||||
# 示例:-XX:HeapDumpPath=/data/dumps/
|
||||
|
||||
# (可选) 在发生 OOM 时执行指定的命令或脚本
|
||||
# 例如,发送告警通知或尝试重启服务(需谨慎使用)
|
||||
# -XX:OnOutOfMemoryError="<command> <args>"
|
||||
# 示例:-XX:OnOutOfMemoryError="sh /path/to/notify.sh"
|
||||
|
||||
# (可选) 启用 GC 开销限制检查
|
||||
# 如果 GC 时间占总时间比例过高(默认 98%)且回收效果甚微(默认小于 2% 堆内存),
|
||||
# 会提前抛出 OOM,防止应用长时间卡死在 GC 中。
|
||||
-XX:+UseGCOverheadLimit
|
||||
```
|
||||
|
||||
这里有几点需要注意:
|
||||
## 其他常用参数
|
||||
|
||||
- **HeapDumpOnOutOfMemoryError** 指示 JVM 在遇到 **OutOfMemoryError** 错误时将 heap 转储到物理文件中。
|
||||
- **HeapDumpPath** 表示要写入文件的路径; 可以给出任何文件名; 但是,如果 JVM 在名称中找到一个 `<pid>` 标记,则当前进程的进程 id 将附加到文件名中,并使用`.hprof`格式
|
||||
- **OnOutOfMemoryError** 用于发出紧急命令,以便在内存不足的情况下执行; 应该在 `cmd args` 空间中使用适当的命令。例如,如果我们想在内存不足时重启服务器,我们可以设置参数: `-XX:OnOutOfMemoryError="shutdown -r"` 。
|
||||
- **UseGCOverheadLimit** 是一种策略,它限制在抛出 OutOfMemory 错误之前在 GC 中花费的 VM 时间的比例
|
||||
- `-server`: 明确启用 Server 模式的 HotSpot VM。(在 64 位 JVM 上通常是默认值)。
|
||||
- `-XX:+UseStringDeduplication`: (JDK 8u20+) 尝试识别并共享底层 `char[]` 数组相同的 String 对象,以减少内存占用。适用于存在大量重复字符串的场景。
|
||||
- `-XX:SurvivorRatio=<ratio>`: 设置 Eden 区与单个 Survivor 区的大小比例。例如 `-XX:SurvivorRatio=8` 表示 Eden:Survivor = 8:1。
|
||||
- `-XX:MaxTenuringThreshold=<threshold>`: 设置对象从新生代晋升到老年代的最大年龄阈值(对象每经历一次 Minor GC 且存活,年龄加 1)。默认值通常是 15。
|
||||
- `-XX:+DisableExplicitGC`: 禁止代码中显式调用 `System.gc()`。推荐开启,避免人为触发不必要的 Full GC。
|
||||
- `-XX:+UseLargePages`: (需要操作系统支持) 尝试使用大内存页(如 2MB 而非 4KB),可能提升内存密集型应用的性能,但需谨慎测试。
|
||||
- -`XX:MinHeapFreeRatio=<percent> / -XX:MaxHeapFreeRatio=<percent>`: 控制 GC 后堆内存保持空闲的最小/最大百分比,用于动态调整堆大小(如果 `-Xms` 和 `-Xmx` 不相等)。通常建议将 `-Xms` 和 `-Xmx` 设为一致,避免调整开销。
|
||||
|
||||
## 5.其他
|
||||
**注意:** 以下参数在现代 JVM 版本中可能已**弃用、移除或默认开启且无需手动设置**:
|
||||
|
||||
- `-server` : 启用“ Server Hotspot VM”; 此参数默认用于 64 位 JVM
|
||||
- `-XX:+UseStringDeduplication` : _Java 8u20_ 引入了这个 JVM 参数,通过创建太多相同 String 的实例来减少不必要的内存使用; 这通过将重复 String 值减少为单个全局 `char []` 数组来优化堆内存。
|
||||
- `-XX:+UseLWPSynchronization`: 设置基于 LWP (轻量级进程)的同步策略,而不是基于线程的同步。
|
||||
- `-XX:LargePageSizeInBytes`: 设置用于 Java 堆的较大页面大小; 它采用 GB/MB/KB 的参数; 页面大小越大,我们可以更好地利用虚拟内存硬件资源; 然而,这可能会导致 PermGen 的空间大小更大,这反过来又会迫使 Java 堆空间的大小减小。
|
||||
- `-XX:MaxHeapFreeRatio` : 设置 GC 后, 堆空闲的最大百分比,以避免收缩。
|
||||
- `-XX:SurvivorRatio` : eden/survivor 空间的比例, 例如`-XX:SurvivorRatio=6` 设置每个 survivor 和 eden 之间的比例为 1:6。
|
||||
- `-XX:+UseLargePages` : 如果系统支持,则使用大页面内存; 请注意,如果使用这个 JVM 参数,OpenJDK 7 可能会崩溃。
|
||||
- `-XX:+UseStringCache` : 启用 String 池中可用的常用分配字符串的缓存。
|
||||
- `-XX:+UseCompressedStrings` : 对 String 对象使用 `byte []` 类型,该类型可以用纯 ASCII 格式表示。
|
||||
- `-XX:+OptimizeStringConcat` : 它尽可能优化字符串串联操作。
|
||||
- `-XX:+UseLWPSynchronization`: 较旧的同步策略选项,现代 JVM 通常有更优化的实现。
|
||||
- `-XX:LargePageSizeInBytes`: 通常由 `-XX:+UseLargePages` 自动确定或通过 OS 配置。
|
||||
- `-XX:+UseStringCache`: 已被移除。
|
||||
- `-XX:+UseCompressedStrings`: 已被 Java 9 及之后默认开启的 Compact Strings 特性取代。
|
||||
- `-XX:+OptimizeStringConcat`: 字符串连接优化(invokedynamic)在 Java 9 及之后是默认行为。
|
||||
|
||||
## 文章推荐
|
||||
## 总结
|
||||
|
||||
这里推荐了非常多优质的 JVM 实践相关的文章,推荐阅读,尤其是 JVM 性能优化和问题排查相关的文章。
|
||||
本文为 Java 开发者提供了一份实用的 JVM 常用参数配置指南,旨在帮助读者理解和优化 Java 应用的性能与稳定性。文章重点强调了以下几个方面:
|
||||
|
||||
- [JVM 参数配置说明 - 阿里云官方文档 - 2022](https://help.aliyun.com/document_detail/148851.html)
|
||||
- [JVM 内存配置最佳实践 - 阿里云官方文档 - 2022](https://help.aliyun.com/document_detail/383255.html)
|
||||
- [求你了,GC 日志打印别再瞎配置了 - 思否 - 2022](https://segmentfault.com/a/1190000039806436)
|
||||
- [一次大量 JVM Native 内存泄露的排查分析(64M 问题) - 掘金 - 2022](https://juejin.cn/post/7078624931826794503)
|
||||
- [一次线上 JVM 调优实践,FullGC40 次/天到 10 天一次的优化过程 - HeapDump - 2021](https://heapdump.cn/article/1859160)
|
||||
- [听说 JVM 性能优化很难?今天我小试了一把! - 陈树义 - 2021](https://shuyi.tech/archives/have-a-try-in-jvm-combat)
|
||||
- [你们要的线上 GC 问题案例来啦 - 编了个程 - 2021](https://mp.weixin.qq.com/s/df1uxHWUXzhErxW1sZ6OvQ)
|
||||
- [Java 中 9 种常见的 CMS GC 问题分析与解决 - 美团技术团队 - 2020](https://tech.meituan.com/2020/11/12/java-9-cms-gc.html)
|
||||
- [从实际案例聊聊 Java 应用的 GC 优化-美团技术团队 - 美团技术团队 - 2017](https://tech.meituan.com/2017/12/29/jvm-optimize.html)
|
||||
1. **堆内存配置:** 建议显式设置初始与最大堆内存 (`-Xms`, -`Xmx`,通常设为一致) 和新生代大小 (`-Xmn` 或 `-XX:NewSize/-XX:MaxNewSize`),这对 GC 性能至关重要。
|
||||
2. **元空间管理 (Java 8+):** 澄清了 `-XX:MetaspaceSize` 的实际作用(首次触发 Full GC 的阈值,而非初始容量),并强烈建议设置 `-XX:MaxMetaspaceSize` 以防止潜在的本地内存耗尽。
|
||||
3. **垃圾收集器选择与日志:**介绍了不同 GC 算法的适用场景,并强调在生产和测试环境中开启详细 GC 日志 (`-Xloggc`, `-XX:+PrintGCDetails` 等) 对于问题排查的必要性。
|
||||
4. **OOM 故障排查:** 说明了如何通过 `-XX:+HeapDumpOnOutOfMemoryError` 等参数在发生 OOM 时自动生成堆转储文件,以便进行后续的内存泄漏分析。
|
||||
5. **其他参数:** 简要介绍了如字符串去重等其他有用参数,并指出了部分旧参数的现状。
|
||||
|
||||
具体的问题排查和调优案例,可以参考笔者整理的这篇文章:[JVM 线上问题排查和性能调优案例](https://javaguide.cn/java/jvm/jvm-in-action.html)。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user