• 垃圾回收机制


    一、什么是垃圾回收

      程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存资源,最终将导致内存溢出,所以对内存资源的管理是非常重要了。

    1、C/C++语言的垃圾回收

      在C/C++语言中,没有自动垃圾回收机制,是通过new关键字申请内存资源,通过delete关键字释放内存资源。

      如果,程序员在某些位置没有写delete进行释放,那么申请的对象将一直占用内存资源,最终可能会导致内存溢出。

    2、Java语言的垃圾回收

      为了让程序员更专注于代码的实现,而不用过多的考虑内存释放的问题,所以,在Java语言中,有了自动的垃圾回收机制,也就是我们熟悉的GC。

      有了垃圾回收机制后,程序员只需要关心内存的申请即可,内存的释放由系统自动识别完成。

      换句话说,自动的垃圾回收的算法就会变得非常重要了,如果因为算法的不合理,导致内存资源一直没有释放,同样也可能会导致内存溢出的。

      当然,除了Java语言,C#、Python等语言也都有自动的垃圾回收机制。

    二、垃圾回收的常见算法

      自动化的管理内存资源,垃圾回收机制必须要有一套算法来进行计算,哪些是有效的对象,哪些是无效的对象,对于无效的对象就要进行回收处理。

      常见的垃圾回收算法有:引用计数法、标记清除法、标记压缩法、复制算法、分代算法等

    1、引用计数法

      引用计数是历史最悠久的一种算法,最早George E. Collins在1960的时候首次提出,50年后的今天,该算法依然被很多编程语言使用。

      1.1  原理

        假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了,可以被回收。

      1.2  优缺点

        优点:

           实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。

           在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember 错误。

           区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。

        缺点:

             每次对象被引用时,都需要去更新计数器,有一点时间开销。

           浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。

           无法解决循环引用问题。(最大的缺点)

        什么是循环引用?

    2、标记清除法

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

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

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

      2.1  原理

        

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

        

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

        

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

      2.2  优缺点

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

      同样,标记清除算法也是有缺点的:

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

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

    3、标记压缩算法

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

      3.1  原理

        

      3.2  优缺点

        优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。

    4、复制算法

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

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

       

      4.1  JVM中年轻代内存空间

        1. 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。

        2. 紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。

        3. 经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。

        4. GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中

      4.2  优缺点

        优点:

          在垃圾对象多的情况下,效率较高

          清理后,内存无碎片

        缺点:

         在垃圾对象少的情况下,不适用,如:老年代内存

            分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低 

    5、分代算法

      前面介绍了多种回收算法,每一种算法都有自己的优点也有缺点,谁都不能替代谁,所以根据垃圾回收对象的特点进行选择,才是明智的选择。

      分代算法其实就是这样的,根据回收对象的特点进行选择,在jvm中,年轻代适合使用复制算法,老年代适合使用标记清除或标记压缩算法。 

    1、什么是垃圾回收?
    程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会
    直占有内存
    资源,最终将导致内存溢出,所以对内存资源的管理是非常重要了。
    1.1C/C++语言的垃圾回收
    C/C++语言中,没有自动垃圾回收机制,是通过new关键字申请内存资源,通过delete
    关键字释放内存资源。
    如果,程序员在某些位置没有写delete进行释放,那么申请的对象将
    直占用内存资源,
    最终可能会导致内存溢出。
    1.2
    Java语言的垃圾回收
    为了让程序员更专注于代码的实现,而不用过多的考虑内存释放的问题,所以,在Java
    言中,有了自动的垃圾回收机制,也就是我们熟悉的GC
    有了垃圾回收机制后,程序员只需要关心内存的申请即可,内存的释放由系统自动识别
    完成。
    换句话说,自动的垃圾回收的算法就会变得非常重要了,如果因为算法的不合理,导致
    内存资源
    直没有释放,同样也可能会导致内存溢出的。
    当然,除了Java语言,C#Python等语言也都有自动的垃圾回收机制。
    2、垃圾回收的常见算法五道
    口互联
    网架构师学院
    第2页,共30页
    自动化的管理内存资源,垃圾回收机制必须要有
    套算法来进行计算,哪些是有效的对
    象,哪些是无效的对象,对于无效的对象就要进行回收处理。
    常见的垃圾回收算法有:引用计数法、标记清除法、标记压缩法、复制算法、分代算法
    等。
    2.1、引用计数法
    引用计数是历史最悠久的
    种算法,最早George E. Collins1960的时候首次提出,50
    年后的今天,该算法依然被很多编程语言使用。
    2.1.1、原理
    假设有
    个对象A,任何
    个对象对A的引用,那么对象A的引用计数器+1,当引用失败
    时,对象A的引用计数器就
    -
    1,如果对象A的计数器的值为0,就说明对象A没有引用了,
    可以被回收。
    2.1.2、优缺点
    优点:
    实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否
    0,就可以直接回收。
    在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报
    outofmember 错误。
    区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。
    缺点:
    每次对象被引用时,都需要去更新计数器,有
    点时间开销。
    浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。
    无法解决循环引用问题。(最大的缺点)
  • 相关阅读:
    流程控制
    小结
    运算符
    进制之间的转换
    自动类型转换和强制类型转换
    变量
    关键字,标识符,
    NGINX 做TCP转发(端口转发)并记录日志
    redash安装
    解决 es CircuitBreakingException 问题(Data too large Error)
  • 原文地址:https://www.cnblogs.com/szhhhh/p/12421478.html
Copyright © 2020-2023  润新知