原文链接:Garbage Collection – 05 – 分代收集算法 (Generational Collection)
相关文章:
接下来我们再来具体了解下分代收集算法 (Generational Collection)
分代收集算法的 GC 分为两种,一种是 Minor GC (新生代 GC),另一种是 Full GC (老年代 GC,也称为 Major GC)
-
新生代 GC (Minor GC)
-
新生代 GC (Minor GC),是指发生在新生代中的垃圾收集动作
-
新生代几乎是大多数 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在新生代中进行的,Java 中的大部分对象都具备朝生夕死的特性,所以 Minor GC 非常频繁,一般回收速度也比较快
-
由于新生代中对象生命周期短,存活率低,所以采用复制算法,成本较低
-
-
老年代 GC (Major GC / Full GC)
-
老年代 GC (Major GC / Full GC),是指发生在老年代中的垃圾收集动作
-
当触发了 Full GC 的时候,通常会伴随着 Minor GC,即对整个堆进行垃圾回收,这便是所谓的 Full GC (Major GC 通常是跟 Full GC 等价的)
-
由于老年代中对象生命周期长,存活率高,没有额外空间进行分配担保,所以采用标记-清除算法或标记-整理算法
-
一、新生代
-
新生代的主要作用是尽可能快速地回收掉那些生命周期短的对象,分为两个区域:Eden 区和两个 Survivor 区
-
Eden 区
-
Eden 代表伊甸园,伊甸园代表着人类的起源,表示当对象刚被创建出来的时候,其内存空间首先会被分配在 Eden 区,如果 Eden 区没有足够空间进行分配时,虚拟机将会发起一次 Minor GC
-
此外,如果 Eden 区还放不下新创建的对象的话,对象也有可能直接被放到 Survivor 区,甚至是老年代中
-
-
两个 Survivor 区
-
两个 Survivor 区,又可以分为 From Survivor 区和 To Survivor 区
-
具体哪个是 From Survivor 区,哪个是 To Survivor 区是不固定的,会随着垃圾回收的进行而相互转换
-
-
由于新生代中的对象 98% 是 “朝生夕死” 的,所以并不需要按照 1:1 的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活着的对象一次性地复制到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才使用过的 Survivor 空间。当 Survivor 空间不够用的时候,则需要老年代进行分配担保
-
下面我们来看看新生代中垃圾回收的具体过程,在这之前,我们先需要了解一个名为对象年龄计数器的概念
-
对象年龄计数器
-
由虚拟机为每个对象进行创建,用于区分哪些对象应该放在新生代中,哪些对象应该放在老年代中
-
如果对象在 Eden 区出生,在经过第一次 Minor GC 后仍然存活,并且能被 Survivor 区容纳的话,将会被复制到 Survivor 区中,同时将对象的年龄设置为 1
-
对象在 Survivor 区中每 “熬过” 一次 Minor GC,年龄就增加一岁,当它的年龄增加到一定程度时 (默认为 15 岁),就会晋升到老年代中
-
对象年龄的阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置
-
-
-
-
新生代垃圾回收过程演示
-
为了方便演示,我们先忽略 Eden 区和 Survivor 区的默认大小比例,并假设每个对象大小都是一样的,Eden 区最多能保存 4 个对象,Survivor 区最多能保存 3 个对象
-
第一次 Minor GC
- 如上所示,当Eden 区被挤满 (有一个存活对象,三个无用对象)时,会触发一次 Minor GC
-
如上所示,当触发 Minor GC 后,如果还有对象存活,且能被 Survivor 区容纳的话,则存活的对象会被复制到其中一块 Survivor 区中 (这里将其复制到 S0 区中),并且将该对象年龄设为 1
-
此时的 S0 区为 From Survivor 区,S1 区为 To Survivor 区
- 如上所示,当复制完成后,Eden 区会被清空
-
第二次 Minor GC
- 如上所示,当Eden 区再次被挤满 (有两个存活对象,两个无用对象)时,会触发第二次 Minor GC
-
如上所示,当触发 Minor GC 后,如果还有对象存活,且能被另一块 Survivor 区容纳的话,则会将 Eden 区中以及 S0 区中的存活对象复制到另一块 Survivor 区中 (这里将其复制到 S1区 中),同时对这些对象的年龄分别 +1
-
此时 S1 区从 To Survivor 区变成了 From Survivor 区,S0 区从 From Survivor 区变成了 To Survivor 区
- 如上所示,当复制完成后,Eden 区以及 S0 区会被清空
-
第三次 Minor GC
- 如上所示,当Eden 区再次被挤满 (有一个存活对象,三个无用对象)时,会触发第三次 Minor GC
- 如上所示,重复上面的步骤,Eden 区以及 S1 区中存活的对象会被复制到 S0 区中,同时对这些对象的年龄分别 +1
- 如上所示,当复制完成后,Eden 区以及 S1 区会被清空
-
以上就是新生代中的垃圾回收过程,会周而复始地不断进行
-
二、老年代
-
老年代中主要存放生命周期较长的对象
-
当触发了 Full GC 的时候,通常会伴随着 Minor GC,即对整个堆进行垃圾回收,这便是所谓的 Full GC,严格来说的话,Full GC 表示的是对整个堆进行垃圾回收,而 Major GC 是对老年代进行垃圾回收,不过通常情况下 Full GC 和 Major GC 是等价的
-
Full GC 的速度比 Minor GC 慢的多,一般会慢 10 倍以上,但执行频率低
三、哪些对象会进入老年代
-
新生成的大对象
- 大对象:指的是需要大量连续内存空间的 Java 对象,最典型的大对象就是那种很长的字符串以及数组 (例如:byte[] 数组)
-
长期存活的对象
- 对象每经历一次 Minor GC,年龄就增加一岁,当它的年龄增加到一定程度时 (默认为 15 岁),就会晋升到老年代中
-
动态对象年龄判断
- 如果在 Survivor 空间中相同年龄的所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象就可以直接进入老年代,而无须等到 MaxTenuringThreshold 中要求的年龄
-
Survivor 区中存放不下的对象
- 对象优先在 Eden 区中分配,当 Eden 区中没有足够的空间分配时,会触发一次 Minor GC,每次 Minor GC 结束后 Eden 区就会被清空,其会将依然存活的对象放到 Survivor 区中,当 Survivor 区中放不下时,则由分配担保机制进入到老年代中
四、触发两种 GC 的条件
-
Minor GC
- Eden 区空间不足
-
Full GC
-
老年代空间不足 ,当创建了一个大对象,如果Eden 区中放不下,就会直接放入在老年代中,如果老年代空间也不足,就会触发一次 Full GC
-
永久代空间不足 (仅针对 JDK7 及以前的版本),当系统中需要加载的类调用的方法很多,同时永久代中没有足够的空间来存放类的元数据,就会触发一次 Full GC;在 JDK8 中,由于取消了永久代,该条件不成立,这也是为什么要用元空间替代永久代的原因之一:为了降低 Full GC 的频率,减少 GC 的负担,提升其效率
-
CMS (一款并发、使用标记-清除算法的gc) GC 时出现 promotion failed、concurrent mode failure ,对于采用 CMS 进行老年代 GC 的程序而言,尤其要注意啊 GC 日志中是否有 promotion failed、concurrent mode failure 这两种情况,当这两种状况出现的时候,可能会触发 Full GC
-
promotion failed:在进行 Minor GC 时,Survivor 区空间放不下存活的对象,对象只能放入老年代中,而此时老年代也放不下,这时候就会造成 promotion failed
-
concurrent mode failure:在执行 CMS GC 的过程中,同时有对象要放入到老年代中,而此时老年代空间不足,这时候就会造成 concurrent mode failure
-
关于 CMS 收集器可以看这里 --> 详解CMS垃圾回收机制
-
-
Minor GC 后晋升到老年代对象的平均大小大于老年代的剩余空间 ,HotSpot为了避免这种情况,在进行 Minor GC 时做了一判断,如果之前统计所得到的 Minor GC 后对象晋升到老年代的平均大小大于老年代的剩余空间,那么就会直接触发 Full GC
- 例如程序第一次触发 Minor GC 后,有 6M 的对象晋升到老年代,当下一次 Minor GC 发生时,首先会检查老年代的剩余空间是否大于 6M,如果小于 6M 则会触发一次 Full GC
-
调用 System.gc(),该方法只是提示虚拟机需要进行垃圾回收,但是否回收则由虚拟机自行决定
-
使用 RMI (远程方式) 来进行 RPC 或管理的 JDK 应用,每小时执行 1 次 Full GC
-
五、JVM 常用的性能调优参数
-
-XX:SurvivorRatio
- Eden 区和 Survivor 区的比值,默认为 8:1
-
-XX:NewRatio
-
老年代和新生代内存大小的比例,默认为 2:1
-
新生代和老年代的大小通过 -Xms (初始堆大小)、-Xmx (最大堆大小) 两个参数来决定的
-
-
-XX:MaxTenuringThreshold
- 对象从新生代晋升到老年代经过 GC 次数的最大阈值