@@ -40,37 +40,38 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G
4040
4141上图所示的 Eden 区、From Survivor0("From") 区、To Survivor1("To") 区都属于新生代,Old Memory 区属于老年代。
4242
43- 大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为大于 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 ` -XX:MaxTenuringThreshold ` 来设置默认值,这个值会在虚拟机运行过程中进行调整,可以通过` -XX:+PrintTenuringDistribution ` 来打印出当次GC后的Threshold 。
43+ 大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为大于 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 ` -XX:MaxTenuringThreshold ` 来设置默认值,这个值会在虚拟机运行过程中进行调整,可以通过` -XX:+PrintTenuringDistribution ` 来打印出当次 GC 后的 Threshold 。
4444
4545> ** 🐛 修正(参见:[ issue552] ( https://github.com/Snailclimb/JavaGuide/issues/552 ) )** :“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。
4646>
4747> ** 动态年龄计算的代码如下**
4848>
4949> ``` c++
5050> uint ageTable::compute_tenuring_threshold (size_t survivor_capacity) {
51- > //survivor_capacity是survivor空间的大小
52- > size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);
53- > size_t total = 0;
54- > uint age = 1;
55- > while (age < table_size) {
56- > //sizes数组是每个年龄段对象大小
57- > total += sizes[age];
58- > if (total > desired_survivor_size) {
59- > break;
60- > }
61- > age++;
62- > }
63- > uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
64- > ...
51+ > //survivor_capacity是survivor空间的大小
52+ > size_t desired_survivor_size = (size_t)((((double)survivor_capacity)* TargetSurvivorRatio)/100);
53+ > size_t total = 0;
54+ > uint age = 1;
55+ > while (age < table_size) {
56+ > //sizes数组是每个年龄段对象大小
57+ > total += sizes[ age] ;
58+ > if (total > desired_survivor_size) {
59+ > break;
60+ > }
61+ > age++;
62+ > }
63+ > uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
64+ > ...
6565> }
6666>
6767> ```
6868
69- 经过这次 GC 后,Eden 区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次 GC 前的“From”,新的"From"就是上次 GC 前的"To"。不管怎样,都会保证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,在这个过程中,有可能当次Minor GC后 ,Survivor 的"From"区域空间不够用,有一些还达不到进入老年代条件的实例放不下,则放不下的部分会提前进入老年代。
69+ 经过这次 GC 后,Eden 区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次 GC 前的“From”,新的"From"就是上次 GC 前的"To"。不管怎样,都会保证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,在这个过程中,有可能当次 Minor GC 后 ,Survivor 的"From"区域空间不够用,有一些还达不到进入老年代条件的实例放不下,则放不下的部分会提前进入老年代。
7070
7171接下来我们提供一个调试脚本来测试这个过程。
7272
7373**调试代码参数如下**
74+
7475```
7576 -verbose: gc
7677-Xmx200M
@@ -85,7 +86,9 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G
8586-XX:+UseConcMarkSweepGC
8687-XX:+UseParNewGC
8788```
89+
8890**示例代码如下:**
91+
8992```java
9093/*
9194* 本实例用于java GC以后,新生代survivor区域的变化,以及晋升到老年代的时间和方式的测试代码。需要自行分步注释不需要的代码进行反复测试对比
@@ -132,15 +135,16 @@ public class JavaGcTest {
132135
133136```
134137
135- 注意:如下输出结果中老年代的信息为 ` concurrent mark-sweep generation ` 和以前版本略有不同。另外,还列出了某次GC后是否重新生成了threshold,以及各个年龄占用空间的大小。
138+ 注意:如下输出结果中老年代的信息为 ` concurrent mark-sweep generation ` 和以前版本略有不同。另外,还列出了某次 GC 后是否重新生成了 threshold,以及各个年龄占用空间的大小。
139+
136140``` bash
1371412021-07-01T10:41:32.257+0800: [GC (Allocation Failure) 2021-07-01T10:41:32.257+0800: [ParNew
138142Desired survivor size 3145728 bytes, new threshold 1 (max 3)
139143- age 1: 3739264 bytes, 3739264 total
140- : 40345K-> 3674K(46080K), 0.0014584 secs] 40345K-> 3674K(199680K), 0.0015063 secs] [Times: user= 0.00 sys= 0.00, real= 0.00 secs]
144+ : 40345K-> 3674K(46080K), 0.0014584 secs] 40345K-> 3674K(199680K), 0.0015063 secs] [Times: user= 0.00 sys= 0.00, real= 0.00 secs]
1411452021-07-01T10:41:32.259+0800: [GC (Allocation Failure) 2021-07-01T10:41:32.259+0800: [ParNew
142146Desired survivor size 3145728 bytes, new threshold 3 (max 3)
143- : 13914K-> 0K(46080K), 0.0046596 secs] 13914K-> 13895K(199680K), 0.0046873 secs] [Times: user= 0.00 sys= 0.00, real= 0.00 secs]
147+ : 13914K-> 0K(46080K), 0.0046596 secs] 13914K-> 13895K(199680K), 0.0046873 secs] [Times: user= 0.00 sys= 0.00, real= 0.00 secs]
144148Heap
145149 par new generation total 46080K, used 35225K [0x05000000, 0x08200000, 0x08200000)
146150 eden space 40960K, 86% used [0x05000000, 0x072667f0, 0x07800000)
150154 Metaspace used 153K, capacity 2280K, committed 2368K, reserved 4480K
151155
152156` ` `
157+
153158! [堆内存常见分配策略 ](./pictures/jvm垃圾回收/堆内存.png)
154159
155160# ## 1.1 对象优先在 eden 区分配
@@ -226,28 +231,28 @@ public class GCTest {
226231
227232> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的 50% 时(默认值是 50%,可以通过 ` -XX:TargetSurvivorRatio= percent` 来设置,参见 [issue1199](https://github.com/Snailclimb/JavaGuide/issues/1199) ),取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。
228233>
229- > jdk8官方文档引用 :https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html 。
234+ > jdk8 官方文档引用 :https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html 。
230235>
231236> ! [](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/image-20210523201742303.png)
232237>
233238> ** 动态年龄计算的代码如下:**
234239>
235240> ` ` ` c++
236241> uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
237- > //survivor_capacity是survivor空间的大小
238- > size_t desired_survivor_size = (size_t)(( ((double)survivor_capacity)* TargetSurvivorRatio)/ 100 );
239- > size_t total = 0 ;
240- > uint age = 1 ;
241- > while (age < table_size) {
242- > // sizes数组是每个年龄段对象大小
243- > total += sizes[age];
244- > if (total > desired_survivor_size) {
245- > break;
246- > }
247- > age++ ;
248- > }
249- > uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
250- > ...
242+ > //survivor_capacity是survivor空间的大小
243+ > size_t desired_survivor_size = (size_t)(( ((double)survivor_capacity)* TargetSurvivorRatio)/ 100 );
244+ > size_t total = 0 ;
245+ > uint age = 1 ;
246+ > while (age < table_size) {
247+ > // sizes数组是每个年龄段对象大小
248+ > total += sizes[age];
249+ > if (total > desired_survivor_size) {
250+ > break;
251+ > }
252+ > age++ ;
253+ > }
254+ > uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
255+ > ...
251256> }
252257>
253258> ```
@@ -283,11 +288,11 @@ public class GCTest {
283288
284289空间分配担保是为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间。
285290
286- 《深入理解Java虚拟机 》第三章对于空间分配担保的描述如下:
291+ 《深入理解 Java 虚拟机 》第三章对于空间分配担保的描述如下:
287292
288- > JDK 6 Update 24 之前,在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 ` -XX:HandlePromotionFailure` 参数的设置值是否允许担保失败(Handle Promotion Failure); 如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的; 如果小于,或者 ` -XX: HandlePromotionFailure` 设置不允许冒险,那这时就要改为进行一次 Full GC。
293+ > JDK 6 Update 24 之前,在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 ` -XX:HandlePromotionFailure` 参数的设置值是否允许担保失败(Handle Promotion Failure); 如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的; 如果小于,或者 ` -XX: HandlePromotionFailure` 设置不允许冒险,那这时就要改为进行一次 Full GC。
289294>
290- > JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小 ,就会进行 Minor GC,否则将进行 Full GC。
295+ > JDK 6 Update 24 之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小 ,就会进行 Minor GC,否则将进行 Full GC。
291296
292297# # 2 对象已经死亡?
293298
@@ -366,10 +371,17 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引
366371
367372# ## 2.4 不可达的对象并非“非死不可”
368373
369- 即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
374+ 即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 ` finalize` 方法。当对象没有覆盖 ` finalize` 方法,或 ` finalize` 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
370375
371376被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
372377
378+ > ` Object` 类中的 ` finalize` 方法一直被认为是一个糟糕的设计,成为了 Java 语言的负担,影响了 Java 语言的安全和 GC 的性能。JDK9 版本及后续版本中各个类中的 ` finalize` 方法会被逐渐弃用移除。忘掉它的存在吧!
379+ >
380+ > 参考:
381+ >
382+ > - [JEP 421: Deprecate Finalization for Removal](https://openjdk.java.net/jeps/421)
383+ > - [是时候忘掉 finalize 方法了](https://mp.weixin.qq.com/s/LW-paZAMD08DP_3-XCUxmg)
384+
373385# ## 2.5 如何判断一个常量是废弃常量?
374386
375387运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?
0 commit comments