• 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。

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

    在这里插入图片描述

  • 相关阅读:
    目标检测算法——3D公共数据集汇总 2(附下载链接)
    在编程中如何使用数字-python
    [程序人生] [世界杯] 程序员世界杯的熬夜调节套餐 - 茶叶篇
    产生 BUG 测试人员需要自己去分析原因吗?
    Qt5开发从入门到精通——第九篇四节( Qt5 文件及磁盘处理—— 文件系统浏览)
    WPS快速截图的方法
    队列——算法专项刷题(七)
    网络编程 - TCP协议
    adobe国际认证证书有用吗?
    【Django】学习笔记
  • 原文地址:https://blog.csdn.net/qq_43331014/article/details/133849213