JVM的垃圾回收机制真是通俗易懂
作者:“来都让一让”
堆内存的划分
分为三个部分(以下名词表示同一个区):
- 新生区、新生代、年轻代
- 养老区、老年区、老年代
- 永久区、永久代
划分区域的目的
唯一目的就是优化GC性能。
如果没有分代,我们所有的对象都放在一块,GC的时候我们需要对堆的所有区域进行扫描。而很多的对象都是“朝生夕死”的,如果把创建的新的对象都放在某一地方,当GC的时候就先把“朝生夕死”对象的区域进行回收,这样就会腾出很多大的空间来。
一、新生区的垃圾回收机制
新生区分为:Eden区、Survivor0区、Survivor1区(也称为from区和to区)
其中Eden区占80%的内存空间,每块Survivor各占用10%的内存空间(如:Eden占800M,每个Survivor占100M)
1.开始时创建的对象都是分配在Eden区域中,当Eden区快满了,就会触发垃圾回收Minor GC(使用复制算法进行垃圾回收)
2.Minor GC处理后,首先会把Eden区中还存活着的对象一次性转入其中一块空闲着的Survivor区。然后清空Eden区,之后创建的对象就继续放入Eden区中了,直至下次Eden又被填满。
3.Eden再次被填满时,就会再次出发Minor GC,清理后(Minor会清理Eden区和Survivor区的内存),Eden区和存在对象的Survivor区(此时的from区)中存活的对象转移到另一块空着的Survivor区中(此时的to区),并清空Eden区和之前存在对象的Survivor区(此时变为to区了,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。)
这就是复制算法的流程。
一直要保持一个Survivor区是空的以提供复制算法垃圾回收,而这块区域的内存只占整块的10%,其他90%内存都可以被使用,课件内存利用率还是相当高的。
二、什么时候进入老年区呢?
1 经历15次GC后进入老年区
默认情况下,如果新生区中的某个对象经历了15次GC后,还是没有被回收掉,那么它就会被转入老年区。
可通过JVM参数“-XX:MaxTenuringThreshold”来设置,默认是15。
2 动态对象年龄判断
这种方法不用等到经历GC15次。
假如一批对象总大小大于当前Survivor区内存的50%,那么大于等于这批对象年龄的对象就会被转移到老年区。
例:假设Survivor0区中的两个对象都经历的3次GC(年龄3),而且这两个对象总大小50M,超过了Survivor0区内存大小的一半。那么此时Survivor0区中年龄大于等于3岁的对象就都要被全部转移到老年区。
3 大对象直接进入老年代
有一个JVM参数"-XX:PretenureSizeThreshold",默认值是0,表示任何情况都先把对象分配给Eden区。
若设置为1048576字节,也就是1M。则表示当创建的对象大于1M时,就会直接把这个对象放入到老年区,就根本不会经过新生区了。
这么做的原因:大对象在经历复制算法进行GC的时候会降低性能。
4 Minor GC后存活的对象太多无法放入Survivor区了
Minor GC后存活的对象太多,导致Survivor区放不下了,此时就会将所有的对象直接转移到老年区中。
三、老年区空间分配担保原则
执行每一次Minor GC前,JVM都先检查一下老年区可用的内存空间是否大于新生区所有对象的总大小。
原因:极端情况下,Minor GC后,新生代中所有的对象都活了下来,那就会把所有新生代中的对象放入老年区中。
- 如果说老年区可用内存大于新生代对象总大小,那么就可以放心的执行Minor GC。
- 但如果老年区内存小于新生区对象的总大小,这时候就会看一个参数:“-XX:HandlePromotionFailure”是否设置为true了。如果为true,就进入下一次判断,看老年区可用内存是否大于之前每次Minor GC后进入老年区对象的平均大小。如果老年代可用内存小于平均大小或是参数没有设置成true,那就会直接触发“Full GC”,就是对老年代进行垃圾回收,腾出空间后,再进行Minor GC,相当于对新生区、老年区统一做了一次清理。
三种情况递进理解:
1.如果Minor GC后,存活的对象<Survivor区大小,直接进入Survivor区即可;
2.如果Minor GC后,存活的对象>Survivor区大小,但<老年区可用内存,直接进入老年区;
3.若Minor GC后,此时老年区都放不下这些存活的对象了,就会触发Full GC;
如果Full GC后老年区内存还是不够用,就会导致OOM内存溢出。
四、老年区垃圾回收算法
标记整理算法
【原理】
一开始对象都是任意分布的,在经历完垃圾回收之后,就会标记出哪些是存活对象,哪些是垃圾对象,然后就会把这些存活的对象在内存中进行整理移动,尽量都挪到一边去靠在一起,然后再把垃圾对象进行清除,这样做的好处就是避免了垃圾回收后产生的大片内存碎片。
【缺点】
较为耗时,比复制算法慢10倍;
所以如果系统频繁出现Full GC,会严重影响系统性能,出现卡顿。所以JVM优化的一大问题就是减少Full GC频率。
五、垃圾回收器
新生区和老年区进行垃圾回收时是通过不同的垃圾回收器进行回收的
Seral 和 Seral Old垃圾回收器
- 分别用于回收新生区和老年区。
- 单线程运行,垃圾回收时会停止我们系统的其他线程,再执行垃圾回收(不再使用);
ParNew和CMS垃圾回收器
- 分别用于新生区和老年区;
- 多线程并发,性能更好,现在一般是线上生产系统的标配。
G1垃圾回收器
统一收集新生区和老年区,采用更加优秀的算法机制。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!