JVM垃圾收集算法与垃圾收集器

        说到GC(Garbage Collection) ,我想Java程序员应该都有所了解,但可能有一大部分初学者仍有些模糊,这篇文章主要从关于GC的三个问题说起:

        那些内存需要回收?

        什么时候回收?

        如何回收?

内存回收的区域

                                                

        关于Java虚拟机内存结构在前文中已经介绍过了,Java虚拟机栈、本地方法栈、程序计数器是线程私有的,它随着线程的启动和销毁而启动或销毁,很显然GC的主要区域并不是这几个线程私有的区域,GC主要区域为方法区与Java堆,这两块区域是线程间共享的,同时堆内存中存放着大量的对象,如果没有GC,那么用不了多长时间就会发生OutOfMemoryError错误。

如何判定对象可以被回收  

      关于什么时候回收的问题,也就是如何判定这个对象需要被回收,先来看看最原始的算法:

        引用计数算法,给对象添加一个引用计数器,每当有一个地方引用它时,引用计数器+1;引用失效时,计数器-1.当一个对象的引用计数器为0,那么可以判定该对象可以被回收。此方法简单高效,但存在着两对象循环引用时不能被回收的缺点。JVM的实现一般不采用这种算法。

        可达性分析算法这种算法主要是通过一系列GC Roots 的对象作为起始点,从这些节点开始向下搜索,,搜索所走过的路径叫引用链,搜索完成后,当一个对象到GC Roots 没有任何引用链时,它就是可以被回收的。

        在Java中可以当做GC  Roots 的对象有:虚拟机栈中引用的对象、方法区中类静态熟悉引用的对象、方法区中常量所引用的对象、本地方法栈中引用的对象。

Java中的引用

        在上面的两种算法中都提到了引用,在Java中有四种引用类型:

        强引用:强引用在程序中十分普遍,类似于“Object obj = new Object()”,这种强引用只要引用还在,对象就永远不会被垃圾收集器回收

        软引用:来描述一些还有用但不是必须的对象,软引用关联的对象只有在要发生内存溢出错误之前才会对软引用的对象进行回收,如果最后还是内存不足,才会抛出内存溢出异常。JDK 1.2 后提供了SoftReference类来实现软引用。

        弱引用:也是描述非必需对象的,它的强度比软引用更弱一些,弱引用关联的对象生命周期只是下次垃圾回收发生之前。当垃圾回收器工作时,它一定被回收JDK 1.2 后提供了WeakReference类来实现弱引用。

        虚引用:最弱的引用关系,一个对象有没有虚引用对它的生命周期没有任何影响,也无法通过虚引用来获得对象实例,虚引用只是可以在对象被垃圾回收器回收时收到一个系统通知。


回收前的自我救赎

        即便是在可达性分析算法中判定不可达的对象,在真正回收之前还要经历两次标记过程,在这期间对象有机会起死回生。

      在可达性分析中如果对象不可达,那么就对其进行第一次标记,并进行一次筛选,筛选条件是对象有没有必要执行finalize()方法,如果对象没有覆盖finalize()方法或已经执行过finalize()方法,那么对象就没有必要执行finalize()方法,对象将“宣告死亡",等待回收。

        如果判定有必要执行finalize()方法,那么对象得到了拯救自己的机会,这个对象将会被虚拟机放入一个F-Queue中,有一个低优先级的线程去执行它。虚拟机只是执行这个方法,并不会承诺等待它运行结束(防止执行缓慢,发生死循环等),稍后GC将会对队列中的对象再进行一次标记,只要对象在finalize()中重新与引用链中任意一个对象获得连接,那么它就被拯救了。注意:finalize()方法只能执行一次。

回收方法区

        很多人认为方法区是不发生GC的,但GC在方法区也是存在的,方法区中回收的主要是废弃常量与无用的类

        如果一个常量没有被任何一个地方引用,如果这是发生GC,如果必要的话这个常量将会被清理。

        无用的类:该类的所有实例对象都已经被回收、加载该类的ClassLoader已经被回收、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。满足上述条件就可以被回收,具体细节可通过虚拟机参数进行控制。

垃圾收集算法

    标记-清除算法(Mark-Sweep)

                            

    复制算法(Copying)

                                 

    标记整理(Mark-Compact)

                               

    分代收集算法

        现在的虚拟机垃圾收集一般都采用分代收集算法,将内存分为新生代与老年代,根据各年代的特点使用不同的算法,IBM公司的研究表明新生代的对象98%都是“朝生夕死”的,所以新生代大多采用复制算法,不用按照1:1比例来划分内存,而是按8:1的比例划分成了 Eden 和Survivor空间,当进行垃圾回收时,Eden中存活的对象将被复制到另一块Survivor中,当Survivor中空间不足时,还要依靠老年代进行分配担保,确保新生代存活下来的对象存入老年代。

                                       

垃圾收集器

                                             

下图中,直线相连的收集器是可以互相配合工作的收集器。

                                             

    Serial收集器

        Serial收集器是一个新生代,也是最基本的、发展历史最悠久的收集器,单线程的收集器,进行垃圾收集时要停掉其他所有的工作进程,在运行在Client模式下的虚拟机来说是一个很好的选择。

    ParNew收集器

        ParNew收集器就是Serial收集器的多线程版本,其他地方与Serial收集器没有太大区别,但它现在也在广泛应用,主要原因是目前只有它能与后面将要提到的CMS收集器配合工作。

    Parallel Scavenge 收集器

         Parallel Scavenge 是一个新生代收集器也是使用的复制算法,并行的多线程收集器,但是它的关注点与其他垃圾收集器不同,CMS等都是尽可能的缩短垃圾收集时用户的等待时间,而Parallel Scavenge收集器主要是达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),“吞吐量优先“收集器。

    Serial Old收集器

        Serial收集器的老年代版本,同样是单线程收集器,使用“标记-整理”算法。

    Parallel Old收集器

        Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法,组成的“吞吐量优先收集器”组合在注重吞吐量和CPU资源敏感的场合可以考虑使用。

    CMS(Concurrent Mark Sweep)收集器

        CMS收集器是一种以获取最短回收停顿时间为目标的多线程收集器,是一种基于“标记-清除”算法的收集器,主要分为四个步骤:

        初始标记 : 仍需要STW(Stop The World),但是速度很快

        并发标记 :耗时较长

        重新标记 :仍需要STW,但速度很快

        并发清除 :耗时较长

        CMS收集器是一款非常优秀的垃圾收集器,但是它也存在一些缺陷:

        ①CMS收集器对CPU资源非常敏感CMS默认启动的线程是(CPU数量+3)/4,当CPU数量达到4个以上时,需要占用25%资源,而当CPU数量不足4个时,会占用大量资源

        ②CMS收集器无法处理浮动垃圾CMS收集器在并发清理时用户进程仍然在不断产生垃圾,而这些垃圾却不能在本次GC中被回收,只能等待下一次GC,这些垃圾就叫做浮动垃圾,也是因为在清理时用户线程还在运行,所以CMS收集器,不能像其他收集器一样等待老年代几乎填满之后才进行收集,需要预留出空间提供给并发收集时程序运行使用。

        ③CMS收集器是一款基于“标记-清除”算法的收集器,所以在GC过程中会产生大量的碎片,空间碎片过多时,明明还有内存空间,却不能给大对象分配,会触发full GC。

    G1(Garbage First)收集器

        G1收集器是一款面向服务端应用的垃圾收集器它和其他收集器相比有如下特点:

        并行与并发:G1能充分利用多CPU,多和环境下的硬件优势,使用多个CPU来缩短STW的停顿的时间,其他收集器需要停顿Java线程执行GC动作,G1仍然可以通过并发的方式让Java程序继续执行。

        分代收集:分代概念在G1收集器中得以保留,虽然G1可以不通过其他收集器配合就可以管理整个GC堆,但是它能够采用不同的方式处理新处理的对象和已经熬过很多轮的对象,达到很好的效果。

        空间整合:由于G1收集器采用的是“标记-整理”算法,G1收集器不会产生内存碎片,能够提供连续的规整的内存。

        可预测的停顿:G1收集器能简历可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,垃圾收集的时间不能超过N毫秒。

        G1收集器避免在整个Java堆中进行全区域的垃圾收集,G1收集器将整个堆分成多个大小相等的独立区域(region),仍保留新生代,老年代的概念,他们不是物理上隔离的了,它们都是一部分region的集合。G1跟踪每个region里面的垃圾价值大小,后台维护一个价值列表,每次根据允许收集的时间优先收集价值最大的region,这也是G1的由来,Garbage-First,使用这个策略可以使G1收集器在有限的时间内获取尽可能高的收集效率。

    G1收集器的运行分为一下四个步骤:

        初始标记

        并发标记

        最终标记

        筛选回收

    


参考资料:周志明《深入理解Java虚拟机--JVM高级特性与最佳实践》    

猜你喜欢

转载自blog.csdn.net/zhang_hongxin/article/details/81029865
今日推荐