|
1 | | - |
2 | | - |
3 | 1 | <!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} --> |
4 | 2 |
|
5 | 3 | <!-- code_chunk_output --> |
|
23 | 21 | - [2.6 如何判断一个类是无用的类](#26-如何判断一个类是无用的类) |
24 | 22 | - [3 垃圾收集算法](#3-垃圾收集算法) |
25 | 23 | - [3.1 标记-清除算法](#31-标记-清除算法) |
26 | | - - [3.2 复制算法](#32-复制算法) |
| 24 | + - [3.2 标记-复制算法](#32-标记-复制算法) |
27 | 25 | - [3.3 标记-整理算法](#33-标记-整理算法) |
28 | 26 | - [3.4 分代收集算法](#34-分代收集算法) |
29 | 27 | - [4 垃圾收集器](#4-垃圾收集器) |
@@ -84,19 +82,22 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G |
84 | 82 | > |
85 | 83 | > ```c++ |
86 | 84 | > uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { |
87 | | -> //survivor_capacity是survivor空间的大小 |
88 | | -> size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100); |
89 | | -> size_t total = 0; |
90 | | -> uint age = 1; |
91 | | -> while (age < table_size) { |
92 | | -> total += sizes[age];//sizes数组是每个年龄段对象大小 |
93 | | -> if (total > desired_survivor_size) break; |
94 | | -> age++; |
95 | | -> } |
96 | | -> uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; |
97 | | -> ... |
| 85 | +> //survivor_capacity是survivor空间的大小 |
| 86 | +> size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100); |
| 87 | +> size_t total = 0; |
| 88 | +> uint age = 1; |
| 89 | +> while (age < table_size) { |
| 90 | +> //sizes数组是每个年龄段对象大小 |
| 91 | +> total += sizes[age]; |
| 92 | +> if (total > desired_survivor_size) { |
| 93 | +> break; |
| 94 | +> } |
| 95 | +> age++; |
| 96 | +> } |
| 97 | +> uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; |
| 98 | +> ... |
98 | 99 | > } |
99 | | -> |
| 100 | +> |
100 | 101 | > ``` |
101 | 102 |
|
102 | 103 | 经过这次 GC 后,Eden 区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次 GC 前的“From”,新的"From"就是上次 GC 前的"To"。不管怎样,都会保证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到老年代中。 |
@@ -181,19 +182,22 @@ public class GCTest { |
181 | 182 | > |
182 | 183 | > ```c++ |
183 | 184 | > uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { |
184 | | -> //survivor_capacity是survivor空间的大小 |
185 | | -> size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100); |
186 | | -> size_t total = 0; |
187 | | -> uint age = 1; |
188 | | -> while (age < table_size) { |
189 | | -> total += sizes[age];//sizes数组是每个年龄段对象大小 |
190 | | -> if (total > desired_survivor_size) break; |
191 | | -> age++; |
192 | | -> } |
193 | | -> uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; |
194 | | -> ... |
| 185 | +> //survivor_capacity是survivor空间的大小 |
| 186 | +> size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100); |
| 187 | +> size_t total = 0; |
| 188 | +> uint age = 1; |
| 189 | +> while (age < table_size) { |
| 190 | +> //sizes数组是每个年龄段对象大小 |
| 191 | +> total += sizes[age]; |
| 192 | +> if (total > desired_survivor_size) { |
| 193 | +> break; |
| 194 | +> } |
| 195 | +> age++; |
| 196 | +> } |
| 197 | +> uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; |
| 198 | +> ... |
195 | 199 | > } |
196 | | -> |
| 200 | +> |
197 | 201 | > ``` |
198 | 202 | > |
199 | 203 | > 额外补充说明([issue672](https://github.com/Snailclimb/JavaGuide/issues/672)):**关于默认的晋升年龄是 15,这个说法的来源大部分都是《深入理解 Java 虚拟机》这本书。** |
@@ -225,7 +229,7 @@ public class GCTest { |
225 | 229 |
|
226 | 230 | ## 2 对象已经死亡? |
227 | 231 |
|
228 | | -堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。 |
| 232 | +堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。 |
229 | 233 |
|
230 | 234 |  |
231 | 235 |
|
@@ -262,6 +266,7 @@ public class ReferenceCountingGc { |
262 | 266 | - 本地方法栈(Native 方法)中引用的对象 |
263 | 267 | - 方法区中类静态属性引用的对象 |
264 | 268 | - 方法区中常量引用的对象 |
| 269 | +- 所有被同步锁持有的对象 |
265 | 270 |
|
266 | 271 | ### 2.3 再谈引用 |
267 | 272 |
|
@@ -342,9 +347,9 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 |
342 | 347 |
|
343 | 348 |  |
344 | 349 |
|
345 | | -### 3.2 复制算法 |
| 350 | +### 3.2 标记-复制算法 |
346 | 351 |
|
347 | | -为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 |
| 352 | +为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 |
348 | 353 |
|
349 | 354 |  |
350 | 355 |
|
@@ -358,7 +363,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 |
358 | 363 |
|
359 | 364 | 当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 |
360 | 365 |
|
361 | | -**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** |
| 366 | +**比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** |
362 | 367 |
|
363 | 368 | **延伸面试问题:** HotSpot 为什么要分为新生代和老年代? |
364 | 369 |
|
@@ -376,7 +381,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 |
376 | 381 |
|
377 | 382 | Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。 |
378 | 383 |
|
379 | | -**新生代采用复制算法,老年代采用标记-整理算法。** |
| 384 | +**新生代采用标记-复制算法,老年代采用标记-整理算法。** |
380 | 385 |
|
381 | 386 |  |
382 | 387 |
|
@@ -388,7 +393,7 @@ Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了 |
388 | 393 |
|
389 | 394 | **ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。** |
390 | 395 |
|
391 | | -**新生代采用复制算法,老年代采用标记-整理算法。** |
| 396 | +**新生代采用标记-复制算法,老年代采用标记-整理算法。** |
392 | 397 |
|
393 | 398 |  |
394 | 399 |
|
@@ -402,7 +407,7 @@ Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了 |
402 | 407 |
|
403 | 408 | ### 4.3 Parallel Scavenge 收集器 |
404 | 409 |
|
405 | | -Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。 **那么它有什么特别之处呢?** |
| 410 | +Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。 **那么它有什么特别之处呢?** |
406 | 411 |
|
407 | 412 | ``` |
408 | 413 | -XX:+UseParallelGC |
@@ -417,7 +422,7 @@ Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它 |
417 | 422 |
|
418 | 423 | **Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。 |
419 | 424 |
|
420 | | -**新生代采用复制算法,老年代采用标记-整理算法。** |
| 425 | +**新生代采用标记-复制算法,老年代采用标记-整理算法。** |
421 | 426 |
|
422 | 427 |  |
423 | 428 |
|
@@ -471,7 +476,7 @@ JDK1.8 默认使用的是 Parallel Scavenge + Parallel Old,如果指定了-XX: |
471 | 476 |
|
472 | 477 | - **并行与并发**:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。 |
473 | 478 | - **分代收集**:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。 |
474 | | -- **空间整合**:与 CMS 的“标记--清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。 |
| 479 | +- **空间整合**:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。 |
475 | 480 | - **可预测的停顿**:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。 |
476 | 481 |
|
477 | 482 | G1 收集器的运作大致分为以下几个步骤: |
|