• JVM调优:JVM中常见的垃圾回收算法详解


    一、什么是垃圾回收?
      Java语言的垃圾回收(Garbage Collection, GC)是Java运行时环境(JRE)提供的一项自动内存管理机制,用于自动释放不再使用的对象所占用的内存。这是Java区别于C和C++等语言的一个重要特性,因为Java程序员不需要显式地分配和释放内存。

    二、如何判断对象是否需要回收?

    判断对象是否存活的两种算法:引用计数法和可达性分析算法

    ①.引用计数法
      Java堆中针对每个对象都设置一个引用计数器。当一个对象被创建并初始化赋值后,该变量计数设置为1。每当有一个地方引用它时,计数器值就加1。当引用失效时,即一个对象的某个引用超过了生命周期(出作用域后)或者被设置为一个新值时,计数器值就减1。任何引用计数为0的对象可以被当作垃圾回收。当一个对象被垃圾回收时,它引用的任何对象计数减1。

    优点:
    1.实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。
    2.在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember错误。
    3.区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。

    缺点:
    1.每次对象被引用时,都需要去更新计数器,有一点时间开销。
    2.浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。
    3.无法解决循环引用问题。

    什么是循环引用问题

    循环引用示例
    class TestA{
    	public TestB b;
    }
    
    class TestB{
      public TestA a;
    }
    
    public class Main{
        public static void main(String[] args){
            A a = new A();
            B b = new B();
            a.b=b;
            b.a=a;
            a = null;
            b = null;
        }
    }
    虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收
    

    ②.可达性分析算法
      可达性分析法也叫根搜索算法,通过称为 GC Roots 的对象作为起点,从上往下进行搜索。搜索所走过的路径称为引用链 (Reference Chain), 当发现某个对象与 GC Roots之间没有任何引用链相连时, 即认为该对象不可达,该对象也就成了垃圾回收的目标。

    在这里插入图片描述

       从GC Roots 开始没有引用链和Obejct5、Object6 和Object7 相连,因此这三个对象对于GC Roots 而言就是不可达的,会被垃圾回收,即便他们互相都有引用。

    三、JVM中常见的垃圾回收算法

    ①.标记清除法
       标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。

    1.标记:从根节点开始标记引用的对象。

    2.清除:未被标记引用的对象就是垃圾对象,可以被清理。

    原理说明:
    在这里插入图片描述

       程序运行期间所有对象的状态,它们的标志位全部是0(也就是未标记,以下默认0就是未标记,1为已标记),假设这会儿有效内存空间耗尽了,JVM将会停止应用程序的运行并开启GC线程,然后开始进行标记工作,按照根搜索算法,标记完以后,对象的状态如下图。

    在这里插入图片描述

       可以看到,按照根搜索算法,所有从root对象可达的对象就被标记为了存活的对象,此时已经完成了第一阶段标记。接下来,就要执行第二阶段清除了,那么清除完以后,剩下的对象以及对象的状态如下图所示。

    在这里插入图片描述
       可以看到,没有被标记的对象将会回收清除掉,而被标记的对象将会留下,并且会将标记位重新归0。接下来就不用说了,唤醒停止的程序线程,让程序继续运行即可。

    优点:
    标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。

    缺点:
    1.效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。

    2.通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

    ②.标记压缩算法

      标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。

    原理说明:
    在这里插入图片描述
       首先也需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。

    优点:
    优点同标记清除算法,解决了标记清除算法的碎片化的问题。

    缺点
    同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。

    ③.复制算法
       复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。

       如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。

    原理说明:
    在这里插入图片描述
    优点:
      复制算法的优点是简单高效,不会出现内存碎片。

    缺点:
      内存利用率低,只有一半的内存被利用。特别是存活对象较多时效率明显降低,因为需要移动每个不可回收数据的内存实际位置。

    ④.分代收集算法
      根据对象的存活周期将内存划分为几块,然后定义回收规则。如图所示,从左到右分别是年轻代(Young Generation)、老年代(Old Generation) 和 永久代(Permanent Generation),另外年轻代又分为了Eden Space(伊甸空间) 、Survivor Space(幸存者空间)。分代收集的算法在当前商业虚拟机算法中被广泛采用。

    在这里插入图片描述
      分代回收算法只是根据对象存活周期的不同,将内存划分为几块。并不是真正一个新的算法,一般分为:老年代(Old Generation)和新生代(Young Generation),老年代就是很少垃圾需要进行回收的,新生代就是有很多的内存空间需要回收,所以不同代就采用不同的回收算法,以此来达到高效的回收算法。

    新生代:每次垃圾收集时都有大批对象死去,只有少量存活,那就选用复制算法。只需要付出少量存活对象的复制成本就可以完成收集。

    老年代:老年代因为对象存活率高、没有额外空间对它进行分配,所以就必须使用 “标记 - 清楚” 或者 “标记 - 整理” 算法来进行回收。

  • 相关阅读:
    react中jest配置,解决node_modules报错esm无法解析的问题
    ECMAScript 2021 (es2020)
    【leetcode】【剑指offer Ⅱ】070. 排序数组中只出现一次的数字
    中秋节,华为云AI送上超级大月亮制作教程,体验赢开发者键鼠套装
    ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的问题解决
    c++day1
    【Spring Cloud】新闻头条微服务项目:引入ElasticSearch建立文章搜索索引
    JS(第十课)JS中的对象
    【vue】axios封装拦截
    软件测试工程师必备:Fiddler 使用十二个小技巧
  • 原文地址:https://blog.csdn.net/A79800/article/details/138683328