JVM 垃圾回收器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014201191/article/details/81871530

 

JVM垃圾收集算法

      JVM垃圾收集算法有四种:标记-清除算法、复制算法、标记-整理算法、分代收集算法

      标记-清除算法:

      该算法如同它的名字一样,分为两个阶段:标记、清除。首先标记出所有需要回收的对象,然后,统一清除这些被标记的对象。该算法的缺点是:1、效率不高;2、产生大量不连续的内存碎片,导致有大量内存剩余的情况下,由于,没有连续的空间来存放较大的对象,从而触发了另一次垃圾收集动作。

      复制算法:

      由于标记-清除算法的效率不高,从而提出了复制算法。复制算法将可用的内存分成两样大小的两块,每次只使用其中一块内存。当这块内存用完之后,就把还存活的对象复制到另外一块上面,然后,把这块清空。复制算法克服了标记-清除算法的两个缺点,但是太浪费内存,相当于内存空间减小了一半。

      随着时间的积累,现在使用的复制算法的虚拟机,不再是把内存分为1:1的两块。因为98%的对象是寿命很短的,创建之后,很快就被回收了,存活下来的只有2%,所以,用来存储存活对象的内存区,可以小一些。现在的商业虚拟机是把可用内存分为一个较大的Eden空间和两个较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,把Eden和Survivor中的存活对象一次复制到另一块Survivor内存区上,然后把Eden和刚才用过的Survivor空间清空。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,这样,每次新产生的对象可以使用90%的内存空间。

      标记-整理算法

      从名字可以看出,该算法是对“标记-清除”算法的改进升级版。同样的该算法分为两个阶段:标记、整理。标记阶段同“标记-清除”算法。整理阶段,不是直接对标记对象进行清理,而是让所有存活的对象都移动到一端,然后,直接把边界以外的内存清空。这就解决了“标记-清除”算法会造成大量不连续内存碎片的问题。

      分代收集算法

      分代收集算法是根据对象的存活周期的不同,将内存划分为几块。当前的商业虚拟机的垃圾收集都采用了该算法。一般把Java堆分成新生代(年轻代)和老年代(年老代)。这样就可以根据各年代中对象的存活周期来选择最合适的收集算法了。新生代,由于只有少量的对象能存活下来,所以选用“复制算法”,只需要付出少量存活对象的复制成本。老年代,由于对象的存活率高,没有额外的空间分担,就必须使用“标记-清除”或“标记-整理”算法。

JVM 垃圾收集器

      由于内存中的对象,是按存活周期存放在不同的内存块中的,所以,我们选择不同的算法来针对不同的内存块进行垃圾收集。从而,对于,不同的内存块,我们需要有不同的垃圾收集器。

      新生代的垃圾收集器有:Serial收集器、ParNew收集器、Parallel Scavenge收集器

      老年代的垃圾收集器有:Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器

      

      下面我们来分别介绍一下这些垃圾收集器

      Serial收集器/Serial Old收集器

      Serial收集器/Serial Old收集器,是单线程的,使用“复制”算法。当它工作时,必须暂停其它所有工作线程。特点:简单而高效。对于运行在Client模式下的虚拟机来说是一个很好的选择。

      ParNew收集器

      ParNew收集器,是Serial收集器的多线程版。是运行在Server模式下的虚拟机中首选的新生代收集器,但它不是默认收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作。

      Parallel Scavenge收集器/Parallel Old收集器

      Parallel Scavenge收集器,也是使用“复制”算法的、并行的多线程收集器。这些都和ParNew收集器一样。但它关注的是吞吐量(CPU用于运行用户代码的时间与CPU总消耗时间的比值),而其它收集器(Serial/Serial Old、ParNew、CMS)关注的是垃圾收集时用户线程的停顿时间。

      Parallel Old收集器是Parallel Scavenge收集器的老年代版本。

      CMS收集器

      CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法。

      CMS收集器分4个步骤进行垃圾收集工作:

      1、初始标记   2、并发标记   3、重新标记   4、并发清除

      其中“初始标记”、“重新标记”是需要暂停其它所有工作线程的。

      G1收集器

      G1(Garbage First)收集器,基于“标记-整理”算法,可以非常精确地控制停顿。

JVM GC Root 引用对象

1、    虚拟机栈中引用的对象(本地变量表)

2、    方法区中静态属性引用的对象

3、    方法区中常量引用的对象

4、    本地方法栈中引用的对象(Native对象)

下面给出GC回收器的搭配情况,只要存在连线,即代表可以搭配使用,暂时没有包含Java9的G1回收器。

JVM 垃圾回收器详解

A. Serial收集器

这个属于串行收集器,是最基本、发展历史最悠久的收集器。这是一个单线程收集器。但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

Serial收集器是历史最悠久的一个回收器,JDK1.3之前广泛使用这个收集器,目前也是ClientVM下 ServerVM 4核4GB以下机器的默认垃圾回收器。串行收集器并不是只能使用一个CPU进行收集,而是当JVM需要进行垃圾回收的时候,需要中断所有的用户线程,知道它回收结束为止,因此又号称“Stop The World” 的垃圾回收器。

特点:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程效率。

串行回收方式适合低端机器,是Client模式下的默认收集器,对CPU和内存的消耗不高,适合用户交互比较少,后台任务较多的系统

Serial收集器默认新旧生代的回收器搭配为Serial+ SerialOld

B. ParNew收集器

ParNew收集器其实就是多线程版本的Serial收集器,它属于并行回收器,但是它不是并发的,同样存在Stop The World的问题,他是多CPU模式下的首选回收器(该回收器在单CPU的环境下回收效率远远低于Serial收集器,所以一定要注意场景哦),但不是Server模式下的默认收集器。

C. ParallelScavenge收集器

ParallelScavenge又被称为是吞吐量优先的收集器,所提到的吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间),假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%。在当今网络告诉发达的今天,良好的响应速度是提升用户体验的一个重要指标,多核并行云计算的发展要求程序尽可能的使用CPU和内存资源,尽快的计算出最终结果,因此在交互不多的云端,比较适合使用该回收器。注意,他是Server模式下的默认收集器。

注意,它要关注的是吞吐量,而其他则关心的是收集的停顿时间

D.SerialOld 收集器(老年代)

SerialOld是旧生代Client模式下的默认收集器,单线程执行;在JDK1.6之前也是ParallelScvenge回收新生代模式下旧生代的默认收集器,同时也是并发收集器CMS回收失败后的备用收集器

E. ParallelOld 收集器(老年代)

ParallelOld是老生代并行收集器的一种,使用标记整理算法、是老生代吞吐量优先的一个收集器。这个收集器是JDK1.6之后刚引入的一款收集器,我们看之前那个图之间的关联关系可以看到,早期没有ParallelOld之前,吞吐量优先的收集器老生代只能使用串行回收收集器,大大的拖累了吞吐量优先的性能,自从JDK1.6之后,才能真正做到较高效率的吞吐量优先,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old收集器

F. CMS 

CMS又称响应时间优先(最短回收停顿)的回收器,使用并发模式回收垃圾,使用标记-清除算法,CMS对CPU是非常敏感的,它的回收线程数=(CPU+3)/4,因此当CPU是2核的实惠,回收线程将占用的CPU资源的50%,而当CPU核心数为4时仅占用25%。

CMS收集器是基于“标记-清除”算法实现的,整个过程分为4个步骤:
①初始标记 Initial Marking
②并发标记 concurrent Marking
③重新标记 remark
④并发清除 concurrent sweep
其中,初始标记,重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只标记一下GC Roots能直接关联到的对象,速度很快。并发标记阶段就是 进行GC Roots Tracing的过程。
重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记几率,这个阶段的停顿时间一般会比初始标记阶段稍长,但远比并发标记时间短。
整个过程耗时最长的阶段是并发标记,并发清除过程,但这两个过程可以和用户线程一起工作。

在初始标记的时候,需要中断所有用户线程,在并发标记阶段,用户线程和标记线程,并发执行,而在这个过程中,随着内存引用关系的变化,可能会发生原来标记的对象被释放,进而引发新的垃圾,因此可能会产生一系列的浮动垃圾,不能被回收。

JVM 垃圾回收器搭配

吞吐量优先的并行收集器

并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。

java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20

-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。

-XX:ParallelGCThreads=20: 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC

-XX:+UseParallelOldGC: 配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100

-XX:MaxGCPauseMillis=100 :设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy

-XX:+UseAdaptiveSizePolicy :设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

响应时间优先的并发收集器

并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。

-XX:+UseParNewGC: 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction: 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

搭配索引图

辅助信息

JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:

-XX:+PrintGC: 输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails: 输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]

-XX:+PrintGCTimeStamps -XX:+PrintGC: PrintGCTimeStamps可与上面两个混合使用 
输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]

-XX:+PrintGCApplicationConcurrentTime: 打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用。输出形式:Application time: 0.5291524 seconds

-XX:+PrintGCApplicationStoppedTime: 打印垃圾回收期间程序暂停的时间。可与上面混合使用。输出形式:Total time for which application threads were stopped: 0.0468229 seconds

-XX:PrintHeapAtGC: 打印GC前后的详细堆栈信息。

 

常见配置汇总

堆设置

  -Xms: 初始堆大小

  -Xmx: 最大堆大小

  -XX:NewSize=n: 设置年轻代大小

  -XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

  -XX:SurvivorRatio=n: 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

  -XX:MaxPermSize=n: 设置持久代大小

收集器设置

  -XX:+UseSerialGC: 设置串行收集器

  -XX:+UseParallelGC: 设置并行收集器

  -XX:+UseParalledlOldGC: 设置并行年老代收集器

  -XX:+UseConcMarkSweepGC: 设置并发收集器

垃圾回收统计信息

  -XX:+PrintGC

  -XX:+PrintGCDetails

  -XX:+PrintGCTimeStamps

  -Xloggc:filename

并行收集器设置

  -XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。

  -XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间

  -XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

并发收集器设置

  -XX:+CMSIncrementalMode: 设置为增量模式。适用于单CPU情况。

  -XX:ParallelGCThreads=n: 设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

响应时间优先的应用: 尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。 在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

吞吐量优先的应用: 尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

猜你喜欢

转载自blog.csdn.net/u014201191/article/details/81871530