JVM 垃圾回收算法详解

1 垃圾回收算法

有四种垃圾回收算法:

  • 标记清除算法
  • 复制算法
  • 标记整理算法
  • 分代回收算法

1.1 标记清除算法

  • 标记:遍历内存区域,对需要回收的对象打上标记。
  • 清除:再次遍历内存,对已经标记过的内存进行回收。

注:蓝色的为存活对象
在这里插入图片描述
缺点:

  • 效率问题;遍历了两次内存空间(第一次标记,第二次清除)。
  • 空间问题:容易产生大量内存碎片,当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC。

1.2 复制算法

将内存划分为等大的两块,每次只使用其中的一块。当一块用完了,或经过一定时间,触发GC时,将该块中存活的对象复制到另一块区域,然后一次性清理掉这块没有用的内存。下次触发GC时将那块中存活的的又复制到这块,然后抹掉那块,循环往复。

优点:

  • 相对于标记–清理算法解决了内存的碎片化问题。
  • 效率更高(清理内存时,记住首尾地址,一次性抹掉)。

在这里插入图片描述
缺点:

内存利用率不高,每次只能使用一半内存,浪费空间。

1.3 标记整理算法

因为前面的复制算法当对象的存活率比较高时,这样一直复制过来,复制过去,没啥意义,且浪费时间。所以针对老年代提出了“标记整理”算法。

  • 标记:对需要回收的进行标记
  • 整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。

如下图可以看到整理后对象都集中在一起,腾出连续的空间。
在这里插入图片描述

优点:不会产生碎片

缺点:每次标记再前移效率偏低

1.4 分代回收算法

分代回收算法本质是上述各算法的结合优化,当前大多商用虚拟机都采用这种分代回收算法。其实大多数对象生命周期非常短,所以在发生GC时,需要回收的对象特别多,存活的特别少,因此需要搬移到另一块内存的对象非常少,所以不需要1:1划分内存空间。而是将整个空间划分为新生代(1/3)和老年代(2/3)。新生代按照8 : 1 : 1的比例划分为三块,最大的称为Eden(伊甸园)区,较小的两块分别称为To SurvivorFrom Survivor(幸存者区或存活区)。

首次GC时,只需要将Eden存活的对象复制到To Survivor。然后将Eden区整体回收。再次GC时,将Eden和To存活的复制到Form Survivor,循环往复这个过程。这样每次新生代中可用的内存就占整个新生代的90%,大大提高了内存利用率。

但不能保证每次存活的对象就永远少于新生代整体的10%,有可能复制过去存不下,所以会有老年代作最后担保,若还不够就会抛出OOM。

堆空间的结构及详细回收流程如下

在这里插入图片描述

  • 堆空间被分成了新⽣代(1/3)和⽼年代(2/3),新⽣代中被分成了eden(8/10)、survivor1(1/10)、survivor2(1/10)

  • 对象的创建在eden,如果放不下则触发minor gc。(minor gc时,会触发 stop the world, 暂停其他用户线程,只让垃圾回收线程工作)。

  • 对象经过⼀次minorGC 后存活的对象会被放⼊到survivor区,并且年龄+1。如果在次执行minorGC,会统一挪到另一个survivor区。如果复制时存不下,则进入老年代。

  • 当survivor区中对象年龄到达15,进⼊到⽼年代。

  • 如果⽼年代内存都满了。会先尝试触发minor gc,再触发Full GC。Full GC执行过程中,STW的时间更长(因为老年代的存活数量比较多)。

  • 如果老年代满了且没有可回收的垃圾,会报OutOfmemory。

某个线程的内存溢出了而抛异常(out of memory),不会让其他的线程结束运行

这是因为当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,进程依然正常

可以打开jdk自带的监视器查看内存分配情况:cmd窗口执行jvisualvm

流程图如下:

在这里插入图片描述

:不是只有幸存区对象年龄超过15才进入老年代,还存在很多其他复杂情况。

1.4.1 对象进入老年代的条件

  • 当遇到一个较大的对象时,就算新生代的伊甸园为空,也无法容纳该对象时,直接进入老年代:⼤对象可以通过参数设置大小,多⼤的对象被认为是⼤对象。-XX:PretenureSizeThreshold

  • 当对象的年龄到达15岁时将进⼊到⽼年代,这个年龄可以通过这个参数设置:-XX:MaxTenuringThreshold

  • 根据对象动态年龄判断,如果s区中的对象总和超过了s区中的50%,那么下⼀次做复制的时候,把年龄⼤于等于这次最⼤年龄的对象都⼀次性全部放⼊到⽼年代。

在这里插入图片描述

  • ⽼年代空间分配担保机制 :在minor gc时,检查⽼年代剩余可⽤空间是否⼤于新生代⾥现有的所有对象(包含垃圾)。如果⼤于等于,则做minor gc。如果⼩于,看下是否配置了担保参数的配置:-XX: -HandlePromotionFailure ,如果配置了担保,那么判断⽼年代剩余的空间是否⼩于历史每次minor gc 后进⼊⽼年代的对象的平均⼤⼩。如果是,则直接full gc,减少⼀次minor gc。如果不是,执⾏minor gc。如果没有担保机制,直接full gc。

    解释:说白了就是要判断下老年代的剩余空间,是否还能承受的住下一次新生代的对象存入老年代。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43331014/article/details/133849213