Skip to content

Commit d735e2a

Browse files
committed
docs: 优化 JVM 专题文档内容
1 parent ce72d2e commit d735e2a

6 files changed

Lines changed: 28 additions & 28 deletions

File tree

docs/java/jvm/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ JVM 是 Java 后端绕不开的核心基础。学习 JVM 的目标不是背概
5151
### 类加载
5252

5353
- [类加载过程详解](./class-loading-process.md):梳理加载、验证、准备、解析、初始化、使用和卸载。
54-
- [类加载器详解(重点)](./classloader.md):理解启动类加载器、扩展类加载器、应用类加载器和双亲委派模型。
54+
- [类加载器详解(重点)](./classloader.md):理解启动类加载器、扩展类加载器(JDK 8)/平台类加载器(JDK 9+)、应用类加载器和双亲委派模型。
5555

5656
### 垃圾回收与调优
5757

docs/java/jvm/jvm-garbage-collection.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public class GCTest {
123123

124124
大部分情况,对象都会首先在 Eden 区域分配。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。
125125

126-
对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置
126+
对象在 Survivor 中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄达到晋升阈值时,就会被晋升到老年代中。`-XX:MaxTenuringThreshold` 用于设置对象晋升年龄的最大阈值,默认值与垃圾收集器有关。例如,JDK 8 中 Parallel GC 的默认值为 15,CMS 的默认值为 6;实际晋升阈值还可能由 JVM 动态调整
127127

128128
> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的 50% 时(默认值是 50%,可以通过 `-XX:TargetSurvivorRatio=percent` 来设置,参见 [issue1199](https://github.com/Snailclimb/JavaGuide/issues/1199)),取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。
129129
>
@@ -241,9 +241,7 @@ public class ReferenceCountingGc {
241241
242242
**对象可以被回收,就代表一定会被回收吗?**
243243
244-
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 `finalize` 方法。当对象没有覆盖 `finalize` 方法,或 `finalize` 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
245-
246-
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
244+
对于重写了 `finalize()` 方法且该方法尚未执行过的对象,HotSpot 在确认对象不可达后,可能会将对应的终结引用加入待处理队列,由 Finalizer 线程异步处理。如果对象在 `finalize()` 方法中重新与引用链上的对象建立关联,它可以暂时逃过回收;否则,后续垃圾收集会再次确认其可回收状态。这个过程通常被概括为“两次标记”,但不代表垃圾收集器会同步等待 `finalize()` 方法执行,也不保证该方法一定会被调用。
247245
248246
> `Object` 类中的 `finalize` 方法一直被认为是一个糟糕的设计,成为了 Java 语言的负担,影响了 Java 语言的安全和 GC 的性能。`Object.finalize()` 从 JDK 9 起被弃用,JEP 421 又在 JDK 18 中将终结机制标记为待移除。新代码不应依赖它。
249247
>
@@ -258,7 +256,7 @@ public class ReferenceCountingGc {
258256
259257
JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
260258
261-
JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱),强引用就是 Java 中普通的对象,而软引用、弱引用、虚引用在 JDK 中定义的类分别是 `SoftReference``WeakReference``PhantomReference`
259+
JDK 1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)。强引用就是程序代码中普遍存在的普通引用赋值,软引用、弱引用、虚引用在 JDK 中定义的类分别是 `SoftReference``WeakReference``PhantomReference`
262260
263261
![Java 引用类型总结](https://oss.javaguide.cn/github/javaguide/java/jvm/java-reference-type.png)
264262
@@ -270,7 +268,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引
270268
String strongReference = new String("abc");
271269
```
272270
273-
如果一个对象具有强引用,那就类似于**必不可少的生活用品**垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
271+
如果一个对象仍然可以通过强引用访问,那就类似于**必不可少的生活用品**垃圾回收器不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 `OutOfMemoryError` 错误,使程序异常终止,也不会靠随意回收强可达对象来解决内存不足问题
274272
275273
**2.软引用(SoftReference)**
276274
@@ -288,7 +286,7 @@ SoftReference<String> softReference2 = new SoftReference<>(new String("def")); /
288286
289287
软引用对象在内存压力较大时可能会被回收,但 JVM 不保证只在内存不足时才清理。唯一强保证是:在抛出 OutOfMemoryError 之前,所有仅被软引用可达的对象一定会被清理。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
290288
291-
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中
289+
软引用可以和一个引用队列(ReferenceQueue)联合使用。垃圾回收器清除软引用后,会在同一时间或稍后把已经注册引用队列的软引用加入对应队列。引用入队表示垃圾回收器已经检测到相应的可达性变化,并不用于证明对象占用的内存已经完成物理释放
292290
293291
**3.弱引用(WeakReference)**
294292
@@ -306,11 +304,11 @@ WeakReference<String> weakReference2 = new WeakReference<>(new String("abc")); /
306304
307305
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。垃圾回收器确定某个对象仅弱可达时,会原子地清除指向该对象的弱引用。不过,清除动作要等到垃圾回收发生时才会执行,因此不保证对象变成弱可达后会立刻被回收。
308306
309-
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中
307+
弱引用可以和一个引用队列(ReferenceQueue)联合使用。垃圾回收器清除弱引用后,会在同一时间或稍后把已经注册引用队列的弱引用加入对应队列
310308
311309
**4.虚引用(PhantomReference)**
312310
313-
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用代码如下:
311+
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用不会阻止垃圾回收器回收其指向的对象。虚引用代码如下:
314312
315313
```java
316314
// --- 示例1 ---
@@ -324,7 +322,7 @@ str = null; // 去除强引用
324322
PhantomReference phantomReference2 = new PhantomReference(new String("abc"), queue); // 匿名对象
325323
```
326324
327-
**虚引用主要用来跟踪对象被垃圾回收的活动**
325+
**虚引用主要用于接收对象可达性发生变化的通知,并配合引用队列安排清理工作**
328326
329327
**虚引用与软引用和弱引用的一个区别在于:** 虚引用通常与引用队列(ReferenceQueue)联合使用。垃圾回收器确定对象进入虚可达状态后,会清除相关虚引用,并在同一时间或稍后将已注册引用队列的虚引用入队。`PhantomReference.get()` 始终返回 `null`,程序不能通过虚引用重新取得对象;它主要用于在对象已无法再被访问后安排清理工作。
330328

docs/java/jvm/jvm-in-action.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ JVM 线上问题排查和性能调优也是面试常问的一个问题,尤其
3535

3636
- **现象**:线上项目刚启动完使用 top 命令查看 RES 占用了超过 1.5G。
3737
- **分析**:整个分析流程用到了较多工作,可以跟着作者思路一步一步来,值得学习借鉴。
38-
- **建议**远离 Hibernate。
38+
- **建议**排查 RES 过高时,应结合堆、线程栈、类元数据和其他本地内存的使用情况定位根因,不能仅根据应用使用了 Hibernate 就得出结论
3939
- **资料**[Linux top 命令里的内存相关字段(VIRT, RES, SHR, CODE, DATA)](https://liam.page/2020/07/17/memory-stat-in-TOP/)
4040

4141
[YGC 问题排查,又让我涨姿势了! - IT 人的职场进阶 - 2021](https://www.heapdump.cn/article/1661497)
@@ -50,7 +50,7 @@ JVM 线上问题排查和性能调优也是面试常问的一个问题,尤其
5050

5151
[你们要的线上 GC 问题案例来啦 - 编了个程 - 2021](https://mp.weixin.qq.com/s/df1uxHWUXzhErxW1sZ6OvQ)
5252

53-
- **案例 1**:使用 guava cache 的时候,没有设置最大缓存数量和弱引用,导致频繁触发 Young GC
53+
- **案例 1**:使用 Guava Cache 时没有设置最大容量,缓存对象持续增长,导致频繁触发 Young GC。是否使用弱引用取决于缓存键或值的生命周期语义,不能将其作为所有缓存的通用配置。
5454
- **案例 2**: 对于一个查询和排序分页的 SQL,同时这个 SQL 需要 join 多张表,在分库分表下,直接调用 SQL 性能很差。于是,查单表,再在内存排序分页,用了一个 List 来保存数据,而有些数据量大,造成了这个现象。
5555

5656
[Java 中 9 种常见的 CMS GC 问题分析与解决 - 美团技术团 - 2020](https://tech.meituan.com/2020/11/12/java-9-cms-gc.html)

docs/java/jvm/jvm-intro.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,9 @@ MaxMetaspaceSize:限制元空间大小上限
238238

239239
在进行回收前就要判断哪些对象还存活,哪些已经死去。下面介绍两个基础的计算方法
240240

241-
1.引用计数器计算:给对象添加一个引用计数器,每次引用这个对象时计数器加一,引用失效时减一,计数器等于 0 时就是不会再次使用的。不过这个方法有一种情况就是出现对象的循环引用时 GC 没法回收。
241+
1. 引用计数器计算:给对象添加一个引用计数器,每次引用这个对象时计数器加一,引用失效时减一,计数器等于 0 时就是不会再次使用的。不过这个方法有一种情况就是出现对象的循环引用时 GC 没法回收。
242242

243-
2.可达性分析计算:这是一种类似于二叉树的实现,将一系列的 GC ROOTS 作为起始的存活对象集,从这个节点往下搜索,搜索所走过的路径成为引用链,把能被该集合引用到的对象加入到集合中。搜索当一个对象到 GC Roots 没有使用任何引用链时,则说明该对象是不可用的。主流的商用程序语言,例如 Java,C#等都是靠这招去判定对象是否存活的
243+
2. 可达性分析计算:从一系列 GC Roots 出发,沿对象之间的引用关系进行搜索,搜索所走过的路径称为引用链。对象之间的引用关系构成的是图,并不是二叉树。当一个对象与 GC Roots 之间不存在任何引用链时,则说明该对象不可达。主流的商用程序语言,例如 Java、C# 等,都会使用这种思路判断对象是否存活
244244

245245
(了解一下即可)在 Java 语言汇总能作为 GC Roots 的对象分为以下几种:
246246

@@ -256,16 +256,16 @@ MaxMetaspaceSize:限制元空间大小上限
256256

257257
首先必须要提到的是一个名叫 **finalize()** 的方法
258258

259-
finalize()Object 类的一个方法一个对象的 finalize()方法只会被系统自动调用一次,经过 finalize()方法逃脱死亡的对象,第二次不会再调用
259+
`finalize()``Object` 类的一个方法一个对象的 `finalize()` 方法至多会被系统自动调用一次;如果对象通过该方法重新建立了可达关系,下一次被判定为不可达时不会再次调用
260260

261261
补充一句:并不提倡在程序中调用 `finalize()` 来进行自救。它的执行时间不确定,甚至不保证一定会执行,而且运行代价高昂,无法保证各个对象的调用顺序。`finalize()` 在 Java 9 中被弃用,并在 Java 18 中被标记为待移除。需要管理堆外资源时,可以根据场景使用 `try-with-resources``java.lang.ref.Cleaner``Cleaner` 本身并不是强、软、弱、幻象引用的统称。
262262

263263
![](https://static001.geekbang.org/infoq/8d/8d7f0381c7d857c7ceb8ae5a5fef0f4a.png)
264264

265-
判断一个对象的死亡至少需要两次标记
265+
对于重写了 `finalize()` 方法且该方法尚未执行过的对象,其处理过程通常被概括为两次标记:
266266

267-
1. 如果对象进行可达性分析之后没发现与 GC Roots 相连的引用链,那它将会第一次标记并且进行一次筛选。判断的条件是决定这个对象是否有必要执行 finalize()方法。如果对象有必要执行 finalize()方法,则被放入 F-Queue 队列中
268-
2. GC 对 F-Queue 队列中的对象进行二次标记。如果对象在 finalize()方法中重新与引用链上的任何一个对象建立了关联,那么二次标记时则会将它移出“即将回收”集合。如果此时对象还没成功逃脱,那么只能被回收了
267+
1. 如果对象经过可达性分析后没有发现与 GC Roots 相连的引用链,它会被第一次标记并接受筛选。对于有必要执行 `finalize()` 方法的对象,HotSpot 可能会将对应的终结引用加入待处理队列,由 Finalizer 线程异步处理
268+
2. 如果对象在 `finalize()` 方法中重新与引用链上的对象建立了关联,后续垃圾收集会将其移出“即将回收”集合;否则,对象仍然可以被回收。这个过程不代表垃圾收集器会同步等待 `finalize()` 方法执行,也不保证该方法一定会被调用
269269

270270
如果确定对象已经死亡,我们又该如何回收这些垃圾呢
271271

docs/java/jvm/jvm-parameters-intro.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ head:
4747

4848
根据[Oracle 官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/sizing.html),在堆总可用内存配置完成之后,第二大影响因素是 `Young Generation` 在堆内存所占的比例。新生代的默认大小会受 JVM 实现、平台、垃圾收集器、堆大小和自适应策略影响,并不存在适用于所有环境的固定“最小 1310 MB”;`NewSize``MaxNewSize` 分别用于约束新生代大小的下限和上限。
4949

50-
可以通过以下两种方式设置新生代内存大小:
50+
对于支持固定新生代大小参数的分代收集器,可以通过以下两种方式设置新生代内存大小:
5151

5252
**1.通过 `-XX:NewSize``-XX:MaxNewSize` 指定**
5353

@@ -70,6 +70,8 @@ head:
7070
-Xmn512m
7171
```
7272

73+
显式设置新生代大小会限制垃圾收集器的自适应调整能力。[Oracle 官方文档](https://docs.oracle.com/en/java/javase/25/docs/specs/man/java.html#extra-options-for-java)明确建议不要为 G1 设置 `-Xmn`;是否需要调整以及如何调整,应结合具体收集器和 GC 日志判断。
74+
7375
GC 调优策略中很重要的一条经验总结是这样说的:
7476

7577
> 尽量让新创建的对象在新生代分配内存并被回收,因为 Minor GC 的成本通常远低于 Full GC。通过分析 GC 日志,判断新生代空间分配是否合理。如果大量新对象过早进入老年代(Promotion),可以适当通过 `-Xmn` 或 -`XX:NewSize/-XX:MaxNewSize` 调整新生代大小,目标是最大限度地减少对象直接进入老年代的情况。
@@ -126,7 +128,7 @@ void MetaspaceGC::initialize() {
126128
}
127129
```
128130

129-
**3、`-XX:MaxMetaspaceSize` 的重要性**如果不显式设置 -`XX:MaxMetaspaceSize`元空间的最大大小理论上受限于可用的本地内存。在极端情况下(如类加载器泄漏导致不断加载类),这确实**可能耗尽大量本地内存**。因此,**强烈建议设置一个合理的 `-XX:MaxMetaspaceSize` 上限**,以防止对系统造成影响
131+
**3、`-XX:MaxMetaspaceSize` 的作用**如果不显式设置 `-XX:MaxMetaspaceSize`元空间默认没有固定上限,持续增长的类元数据可能消耗大量本地内存。是否设置该参数以及设置多大,应结合类元数据使用情况和进程的本地内存预算决定。上限过小会增加元数据 GC 的频率,也可能提前触发 `OutOfMemoryError: Metaspace`,因此不存在适用于所有应用的推荐值
130132

131133
相关阅读:[issue 更正:MaxMetaspaceSize 如果不指定大小的话,不会耗尽内存 #1204](https://github.com/Snailclimb/JavaGuide/issues/1204)
132134

@@ -240,8 +242,8 @@ JDK 9 及之后应使用统一 JVM 日志框架 `-Xlog`。例如,下面的配
240242

241243
本文为 Java 开发者提供了一份实用的 JVM 常用参数配置指南,旨在帮助读者理解和优化 Java 应用的性能与稳定性。文章重点强调了以下几个方面:
242244

243-
1. **堆内存配置:** 建议显式设置初始与最大堆内存 (`-Xms`, -`Xmx`通常设为一致) 和新生代大小 (`-Xmn` `-XX:NewSize/-XX:MaxNewSize`),这对 GC 性能至关重要
244-
2. **元空间管理 (Java 8+)** 澄清了 `-XX:MetaspaceSize` 的实际作用(触发元数据 GC 的初始高水位阈值,而非初始容量),并强烈建议设置 `-XX:MaxMetaspaceSize` 以防止潜在的本地内存耗尽
245+
1. **堆内存配置:** 可以根据部署环境显式设置初始与最大堆内存(`-Xms``-Xmx`服务端应用通常设为一致)。新生代大小是否需要显式设置,应结合垃圾收集器和 GC 日志判断;G1 通常不建议设置 `-Xmn`
246+
2. **元空间管理Java 8+** `-XX:MetaspaceSize` 用于设置触发元数据 GC 的初始高水位阈值,而不是元空间的初始容量。`-XX:MaxMetaspaceSize` 可以限制类元数据使用的本地内存,但具体上限需要根据应用实际情况确定
245247
3. **垃圾收集器选择与日志:**介绍了不同 GC 算法的适用场景,并强调在生产和测试环境中开启详细 GC 日志对于问题排查的必要性;JDK 8 使用传统 GC 日志参数,JDK 9 及之后使用 `-Xlog`
246248
4. **OOM 故障排查:** 说明了如何通过 `-XX:+HeapDumpOnOutOfMemoryError` 等参数在发生 OOM 时自动生成堆转储文件,以便进行后续的内存泄漏分析。
247249
5. **其他参数:** 简要介绍了如字符串去重等其他有用参数,并指出了部分旧参数的现状。

0 commit comments

Comments
 (0)