JVM垃圾回收机制

新生代垃圾回收

​ 新生代我们预先分配的内存空间,几乎都被全部对象给占满了!此时假设我们代码继续运行,他需要在新生代 里去分配一个对象,怎么办?发现新生代里内存空间都不够了! 这个时候,就会触发一次新生代内存空间的垃圾回收,新生代内存空间的垃圾回收,也称之为“Minor GC”,有的时候我们 也叫“Young GC”,他会尝试把新生代里那些没有人引用的垃圾对象,都给回收掉。

新生代变成老年代

​ 如果一个实例对象在新生代中,成功的在15次垃圾回收(young gc)之后,还是没被回收掉,就说明他已经15岁了,然后他会被转移到Java堆内存的老年代中去

新生代垃圾回收算法为:标记删除法、复制算法、分代回收算法

标记清除算法

在这里插入图片描述
步骤
1、先找到GC roots根来遍历将非垃圾对象进行标记。
2、他会将垃圾进行清除,就是图中的情况。

注意:jvm并不是真正的把垃圾对象进行了遍历,把内部的数据都删除了,不是这样的,而是把垃圾对象的首地址和尾地址进行了保存,等到再次分配内存时,直接去地址列表中分配,所以清除的效率高。

优点:清除速度快,效率高。

缺点:会产生大量的内存碎片(就是很多不连续的内存空间),如果放入一个大的数组的时候,没有连续的内存放置大的数组,就会出现内存溢出,但是所有的内存碎片加起来可以放置一个大的对象,所有说内存使用率低,造成内存不连续。

复制算法

在这里插入图片描述
注意:左边是from区,右边是to区

步骤
1、先找到GC roots根来遍历将非垃圾对象进行标记。
2、然后将from区的非垃圾对象进行复制,到to区,并且进行整理,整理完成之后会将from区的数据进行清空,然后交换from和to区,使得from区一直是存储数据,to区一直空白的。

优点:也不会出现内存碎片
缺点:有效内存只有一半,只有一半存储数据,太浪费空间

分代回收算法

在这里插入图片描述
!(/images/20200522141123556.png)
对象头有个参数为分代年龄

新生代内存区域划分:一般情况下,新生代占1/3,老年代占2/3,新生代分为一块Eden区域和两块Survivor区域,一般为Eden区域占80%,两块Survivor区域分别占10%,一般Eden区域进行垃圾回收后存活下来的对象约1%左右转移到Survivor区域

对象年龄判断

​ 新生代中的对象每经过一次GC,其年龄对象就会增长一岁,当经过15次GC后,即年龄对象达到15岁就会被移到老年代,具体多少岁进入老年代,可以通过JVM参数**-XX:MaxTenuringThreshold**,默认是15

动态对象年龄判断

​ 存在另一个规则可以使对象不用等待15次GC过后才进入老年代,大致规则是假如当前对象的Survivor区域里,一批对象的总大小大于这块Survivor区域的内存大小的50%,那么此时大于等于这批年龄的对象,就可以直接进入老年代,另外这里要理清楚一个概念,就是实际这个规则运行的时候是如下的逻辑:年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区 域的50%,此时就会把年龄n以上的对象都放入老年代。

大对象直接进入老年代

​ 有一个JVM参数,就是**-XX:PretenureSizeThreshold**,可以把他的值设置为字节数,比如“1048576”字节,就是1MB。他的意思就是,如果你要创建一个大于这个大小的对象,比如一个超大的数组,或者是别的啥东西,此时就直接把这个大对象放到老年代里去,压根儿不会经过新生代。就是要避免新生代里出现那种大对象,然后屡次躲过GC,还得把他在两个Survivor区域里来回复制多次之后才能进入老年代,在内存中多次复制会耗费时间

Minor GC后的对象太多无法放入Survivor区怎么办?

​ 如果在Minor GC之后发现生育的存活对象太多,没办法放入另一块的Survicor区时,会将这些对象直接转移到老年代去

老年代垃圾回收

​ 老年代垃圾回收算法为:标记整理法

标记整理法

在这里插入图片描述
步骤
1、先找到GC roots根来遍历将非垃圾对象进行标记。
2、它会将垃圾进行清除,并且会将非垃圾对象进行向前移动,使得内存紧凑。

优点:不会出现内存碎片,提高了内存的利用率
缺点:清除速度慢,因为在整理期间会有对象的拷贝和移动,并且引用内存的对象的地址要进行改变。

老年代空间分配担保规则

​ 如果新生代里有大量对象存活下来,确实在自己的Survivor区域中放不下了,必须转移到老年代,那么如果老年代的空间也不够放下这些对象了,此时JVM在执行任何一次Minor GC之前,会先检查一下老年代的可用内存空间是否大于新生代所有对象的总大小

​ 为啥检查这个呢?因为最极端的情况下,可能新生代Minor GC过后,所有对象都存活下来了,那岂不是新生代所有对象全部要进入老年代?如果说发现老年代的内存大小是大于新生代所有对象的,此时就可以放心大胆的对新生代发起一次Minor GC了,因为即使Minor GC之 后所有对象都存活,Survivor区放不下了,也可以转移到老年代去。但是假如执行Minor GC之前,发现老年代的可用内存已经小于了新生代的全部对象大小了,所以假如Minor GC之前,发现老年代的可用内存已经小于了新生代的全部对象大小了,就会看一个**-XX:- HandlePromotionFailure**的参数是否设置了,如果有这个参数,那么就会继续尝试进行下一步判断,就是看看老年代的内存大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小,之前每次Minor GC后,平均都有10MB左右的对象会进入老年代,那么此时老年代可用内存大于10MB。这就说明,很可能这次Minor GC过后也是差不多10MB左右的对象会进入老年代,此时老年代空间是够的

如果新生代的对象大于老年代剩余的空间并且没有设置**-XX:- HandlePromotionFailure参数,此时就会触发一次Full GC,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC。如果设置了-XX:- HandlePromotionFailure**参数,此时就冒险尝试一下Minor GC,此时进行Minor GC有几种可能:

  1. 第一种可能,Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的,那么此时存活对象进入Survivor 区域即可。

  2. 第二种可能,Minor GC过后,剩余的存活对象的大小,是大于 Survivor区域的大小,但是是小于老年代可用内存大小 的,此时就直接进入老年代即可。

  3. 第三种可能,很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内 存的大小。此时老年代都放不下这些存活对象了,就会发生“Handle Promotion Failure”的情况,这个时候就会触 发一次Full GC,Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的 “OOM”内存溢出了

    image-20201117191911086

老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍,如果系统频繁出现老年代的Full GC垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况。

思考题

  • 到底什么时候会尝试触发Minor GC?

    当需要在新生代中分配一个对象,但是发现新生代里的内存空间不足够时发生Minor GC

  • 触发Minor GC之前会如何检查老年代大小,涉及哪几个步骤和条件?

首先检查老年代是否能够存下所有新生代的大小,极端情况下新生代Minor GC后所有对象存活,如果能存下则直接进行Minor GC,不能存在则进一步判断是否设置了**-XX:- HandlePromotionFailure**参数,如果设置了该参数则判断平均每次GC存活的对象大小,如果老年代能放下则冒险一试

  • 什么时候在Minor GC之前就会提前触发一次Full GC?

    • 大对象无法存在在Eden区域,Survivor区域也放不下,此时判断老年代能否放下,如果老年代也放不下则触发一次Full GC
    • Minor GC后,生余的存活对象大于Survivor区域,老年代也存不下新生代的对象,此时会触发Full GC
  • Full GC的算法是什么?

    标记整理算法

  • Minor GC过后可能对应哪几种情况?

    • Minor GC后Survivor区域能放下
    • Minor GC后Survivor区域能放不下,老年代能放下,直接进入老年代
    • Minor GC后Survivor区域能放不下,老年代也放不下,触发Full GC,同时进行Minor GC,判断是否能放下新生代存活对象,不能放下直接OOM
  • 哪些情况下Minor GC后的对象会进入老年代?

    • 对象年龄达到15岁
    • 动态年龄判断超过该Survivor区域内存50%最老的一批对象
    • Minor GC后Survivor区域放不下,直接进入老年代