JVM调优-----垃圾收集

 摘要: 

  自动垃圾回收处理时Java语言的一大特点,了解垃圾收集处理,有助于合理给我们的Java程序选择合适的JVM参数,让我们的程序更快,更稳定的运行。

  垃圾回收,本质来说就是释放系统内存资源。当我们的程序使用完内存资源以后,要及时把曾经占用过内存释放掉,以保证程序在运行的过程中有足够的内存资源使用。如果分配完内存资源后,却没有释放完全内存,就会引起内存泄漏。Java里面的垃圾回收,其实就是释放无用的对象所占有的内存。


关于垃圾收集,我们要弄清楚几个问题:

  1. 什么对象需要回收?
  2. 何时回收这些对象?
  3. 怎样回收这些对象?

垃圾回收算法与思想:

  1.     引用计数法(Reference Counting)

    最早的垃圾收集方式,其实现很简单,对于一个对象X,只要有任何一个对象引用了X,X的引用计数器就+1,当引用失效的时候就-1,当引用计数器为0的时候,说明这个对象X就可能不被使用了。但是它有一个问题,它无法处理循环引用的情况:当两个垃圾对象相互引用的时候就无法被垃圾回收器识别,从而引起内存泄漏。Java垃圾回收器中,没有使用这种算法。如图:

                                                        

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

          标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。标记阶段通过根节点开始,标记所有从根节点可达的对象。因此未被标记的对象就是未被引用的对象,然后在清除阶段的时候,清除所有未被标记的对象。这种算法也有一个问题:就是会存在空间碎片,回收后的空间是不连续的。在后续分配大对象的时候,不连续的内存空间工作效率会低于连续的空间。

     3.      复制算法(Copying)

          它与标记清除算法相比,是一种高效的回收方法:将原有的内存空间分为两块,每次只使用一块空间,将已标记的对象复制到另一块空间,然后清除原来的空间里面的所有对象,交换内存角色,从而完成垃圾回收。它很好的解决了标记清除算法的空间碎片问题,但是它的代价缺点是将系统内存折半。在Java新生代串行垃圾回收器中,就是使用了该算法,因为在新生代中,垃圾对象通常会多于存活对象,复制算法的效果会比较好。

   4.      标记-压缩算法(Mark-Compact)

         在老年代中,大部分对象是存活对象,因此如果使用复制算法,需要复制的对象会比较多,成本会比较高。标记-压缩算法在标记清除算法的基础上多了压缩的过程。在标记完成后,将所有存活的对象压缩到内存的一端,然后清理边界外所有的空间。这样避免了碎片的产生,又不需要两块相同的内存空间,性价比会更高。

  5.     增量算法(Incremental Collecting)

        在垃圾回收过程中,程序会处于一种Stop the World状态,在这种状态下,程序所有线程会被挂起,暂停工作,等待垃圾回收完成。因此如果垃圾回收时间很长的时候,程序会暂停运行很长时间,这样会严重影响用户体验和系统稳定性。

       增量算法的思想是让垃圾回收线程和程序线程交替执行,每次垃圾回收只收集一小片内存空间,接着切换到应用程序线程。反复执行,直到垃圾收集完成。这种方式能减少系统的停顿时间,但是线程切换和上下文转换的消耗,会使垃圾回收的总体成本上升,造成系统吞吐量下降。

6.     分代(Generational Collecting)

       分代算法思想是结合上述算法的特点,根据不同对象的特点,分为多个内存区间,对不同内存区间对象的特点,给每个区间选择合适它们的算法,使用不同的回收算法,以提高垃圾回收效率。像对年轻代使用复制算法,对老年代使用标记压缩算法。即:内存分代,不同代使用不同算法。目前HotSpot虚拟机广泛使用这种算法。


垃圾收集器的类型

    1、按线程数分:串行垃圾回收器和并行垃圾回收器。串行垃圾回收器一次只用一个线程进行垃圾回收;并性垃圾回收器一次将开启多个线程同时进行垃圾回收,在并行能力较强的CPU上,并行垃圾回收器能缩短GC的停顿时间。

    2、按工作模式分:可用分为并发垃圾回收器和独占式垃圾回收器。并发垃圾回收器可用和应用程序交替工作,独占式的垃圾回收处理器一旦运行,就要停止程序所有线程,直到垃圾回收完成。

   3、按碎片处理方式:压缩式垃圾回收器和非垃圾回收器。收集完成后有无多存活对象进行压缩整理区分。

   4、按内存区间:可分为新生代垃圾回收器和老年代垃圾回收器。它们的工作区间不一样。


评价GC策略的指标

   1、吞吐量:指程序花费的时间占系统总运行时间的比值。对于后台服务程序,对吞吐量关注比较多。

   2、垃圾回收负载:指垃圾回收时间占系统运行时间的比值。

   3、停顿时间:垃圾回收运行时,程序暂停的时间。对于用户应用程序,程序暂停的时间越短,用户体验会越好。

   4、垃圾回收频率:垃圾回收器多久运行一次,一般来说时频率越低越好的,像增加堆内存大小,能降低频率,但是停顿时间增长

   一般很难在所有指标上都达到最优,一般根据应用本身特点,尽量调整垃圾回收器配合程序工作。


常用垃圾收集器

新生代串行收集器:

   1、最基本垃圾收集器,特点:单线程串行回收;独占式垃圾回收

   2、缺点:垃圾回收过程,应用会暂停服务。会造成糟糕的用户体验

   3、优点:实现简单,逻辑处理高效,没有线程切换的开销

   4、使用场景:单CPU处理器,或者应用内存比较小的场合,性能会特别好

   5、启用参数:-XX:+UseSerialGC ,可以指定新生代和老年代使用串行收集器

老年代串行收集器

   1、使用标记-压缩算法、独占式垃圾回收

   2、老年代的垃圾回收通常比年轻代回收时间更长,启动时可能会导致程序停顿几秒甚至更长时间

   3、优点:同串行收集器优点

   4、使用场景:可以和多种新生代垃圾回收器配合使用,同时可以做为CMS回收器的备用回收器

   5、启用参数:-XX:+UseParNewGC :新生代使用并行垃圾收集器,老年代使用串行收集器

                          -XX:+UseParallelGC: 新生代使用并行回收收集器,老年代使用串行垃圾收集器

并行收集器:

   1、简单的把串行回收器多线程化。独占式

   2、回收策略,算法, 参数和串行回收器一样

   3、在并发能力比较强的CPU上,停顿时间要短于串行收集器;但是在并发能力弱的CPU上,效果不一定比串行收集器好,甚至由于多线程压力,实际表现可能比串行回收器还要差。

  4、启用参数:-XX:+UseParNewGC:新生代使用并行收集器,老年代使用串行回收器

                         -XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用CMS

                        -XX:ParallelGCThreads:并行的线程数,一般最好根CPU数量相当,避免过多的线程数,影响垃圾收集性能

新生代并行回收(Parallel Scavenge)收集器(注意跟并行收集器区分开)

  1、使用复制算法,重点关注系统吞吐量

  2、启用参数:-XX:+UseParallelGC:新生代是哦那个并行回收收集器,老年代使用串行收集器

                         -XX:+UseParallelOldGC:新生代老年代都是用并行回收收集器

 3、通过两个参数控制系统吞吐量:

      1)-XX:MaxGCPauseMillis: 设置最大垃圾收集停顿时间,收集器在工作时会调整Java堆大小和其他参数,尽可能把停顿时间控制在这个范围内。如果为减少停顿时间而把这个值调整得很小,JVM可能会使用一个较小的堆,这样会导致频繁GC,从而增加了垃圾回收的总时间,降低了总吞吐量。

      2)-XX:GCTimeRatio:设置吞吐量大小,0-100之间。设置为n,这时系统则会用不超过1/(1+n)时间来进行垃圾收集。

4、与并行收集器其他不同点:此收集器支持自适应GC调整策略。-XX:+UseAdaptiveSizePolicy打开,会自动调整各代(新生代,老年代)大小,比例来达到堆大小、吞吐量和停顿时间的平衡点。这时候我们只需要指定最大堆,目标吞吐量和停顿时间就可以了。

老年代并行回收收集器

  1、同上新生代并行回收器,对老年代启动。

  2、启动参数:-XX:+UseParallelOldGC: 新生代老年代都是用并行回收收集器

                          -XX:ParallelGCThreads:设置并行的线程数,

  

CMS收集器(Concurrent Mark Sweep,并发标记清除)

   1、关注停顿时间、使用标记清除法,多线程并行回收,非独占式

   2、工作过程:初始标记、并发标记、重新标记、并发清除和并发重置;(其中初始标记和重新标记是独占式的,其他过程与用户线程一起执行)

   3、三种标记都是标记需要回收的对象,标记完后正式回收对象;并发重置是指垃圾回收完后,重新初始化CMS数据结构和数据,为下一次垃圾回收做准备。

   4、由于与应用线程并发执行,存在CPU相互抢占,CMS期间对应用程序吞吐量有一定影响,如果CPU资源紧张的时候,受CMS线程影响,程序的性能可能会很糟糕。

   5、因为不是独占式,在CMS回收过程,程序还是会不断产生新的垃圾,这些新生成的垃圾在回收过程式无法清除的。因此在CMS回收过程,要确保程序还有内存可用,这导致了CMS收集器不会在堆内存饱和的时候才去垃圾回收,在一个特定阀值的时候就会触发CMS垃圾回收,确保程序还有内存可用。阀值可通过:-XX:CMSInitiatingOccupancyFraction指定,默认式68,即当老年代空间使用率到达68的时候开始执行一次CMS回收。

   6、如果在执行过程出现内存不足的情况,JVM会启用老年代串行收集器来进行垃圾回收(上文说到的用做CMS备用收集器)。因此,根据应用程序特点,可以对该参数进行调优,当内存增长比较满的时候,设置一个大的值可用减少老年代回收次数,这样能较为明显的改善程序性能。

  7、因为标记-清除算法会产生空间碎片,因此,当内存空间比较大的时候,仍有可能被迫执行一次垃圾回收,来换取一片较大的内存空间,这种现象对程序十分不利,对此,CMS提供了-XX:+UseCMSCompactAtFullCollection 来指定多少次CMS后执行一次内存压缩,以清除空间碎片。

G1收集器(Garbage First)

   1、最新的垃圾收集器,其目标式作为一款服务端垃圾收集器,在吞吐量和听停顿时间的控制上要求优于CMS回收器

   2、使用标记-压缩算法,因此不会有空间碎片,因此没有必要完成清除后,进行独占式的压缩操作,能进行精确的停顿控制。

   3、优势:在可预测的停顿时间,能够尽可能快地在指定时间内完成回收任务。(jstat可以查看垃圾回收情况)

   3、启用参数: -XX:+UnlockExperimentalVMOptions -XX:UseG1GC

                            -XX:MaxGCPauseMillis = n   

                            -XX:GCPauseIntervalMillis = m   指定在m毫秒内,停顿时间不超过n毫秒   


总结:

   垃圾收集器用程序运行时回收无用的对象,以释放内存,确保程序有分配的内存得以完全释放,防止内存泄漏。我们应当学习垃圾收集器的原理,根据我们应用程序的需要合理的调整我们的垃圾处理策略,选择合适的垃圾处理器,来确保程序的正常运行和提高程序性能。(下次总结一下常见的调优参数,以及调优技巧)

猜你喜欢

转载自blog.csdn.net/aa792978017/article/details/89068451