首页 > 代码库 > 深入理解_JVM内存管理垃圾收集器05

深入理解_JVM内存管理垃圾收集器05

1、垃圾收集器(内存回收方法的具体实现):
名词解释:
     并行(Parallel):多条垃圾线程并行工作,但是此时用户线程仍然处于等待状态。
     并发(Concurrent):指用户线程与垃圾收集线程同时执行(并不一定是并行的,可能会交替执行),用户程序继续运行,而垃圾收集程序运行于另一个CPU上。
 
     HotSpot虚拟机包含的所有收集器如下图:
技术分享
     说明:
     (a)JDK1.6_Update14之后引入了Early Access版G1收集器。
     (b)如果两个收集器之间存在连线,就说明它们可以搭配使用。
 
技术分享
 
      <Young generation>:
          (1)串行收集器(Serial):
               (a)串行收集使用单线程,复制算法处理所有垃圾回收工作,因为无需多线程交互,实现容易,而且效率比较高。但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。
               (b)采用串行GC时,SurvivorRadio的值对应Eden Space/Survivor Space,SurvivorRadio默认的值为8。例如:当-Xmn为10MB时,Eden Space为8MB,2个Survivor Space各为1MB。
               (c)目前仍然是Client模式下的新生代的默认收集方式。
               (d)通常存活的对象在Minor GC之后,不会直接进入旧生代,而是需要经过几次Minor GC之后仍然活着,才会进入老生代。这个在Minor GC中存活的最大次数在串行和ParNew方式时可通过:
-XX:MaxTenuringThreshold来设置。
          运行示意图如下:
技术分享
               缺点:进行回收时,必须暂停整个运行环境。
 
          (2)ParNew收集器: 
               (a)并行收集就是Serial的多线程版本,也采用复制算法,使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。
               (b)默认情况下它开启的收集线程数同CPU数量相同。在服务器CPU过多的情况下,可以使用:-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
               (c)ParNew也是通过SurvivorRatio值来划分空间,在开启UseAdaptiveSizePolicy后,则在每次Minor GC后动态计算Eden、to的大小。
               (d)server模式下新生代默认使用该模式。
               (e)与Parallel Scavenge区别:
                    <1>ParNew须配合老生代使用CMS GC,且ParNew GC不可与Parallel Old同时使用。
                    <2>在配置为使用CMS GC的情况下,新生代默认采用并行GC方式,也可通过:
-XX:+UseParNewGC来强制指定。
                    
          运行示意图如下:
技术分享
               缺点:进行回收时,需要暂停整个运行环境。 
          
          (3)Parallel Scavenge收集器(称为:吞吐量优先收集器):
               <1> 该收集器也是一个新生代收集器,它也是使用复制算法的收集器。
               <2> 默认情况下Eden、S0、S1的比例划分采用的为InitialSurvivorRatio,此值默认为8.可通过-XX:InitialSurvivorRatio进行调整。在Sun JDK1.6.0后也可通过-XX:SurvivorRatio来调整,但并行回收GC会将此值+2赋给InitialSurvivorRatio。当同时配置了:InitialSurvivorRatio和SurvivorRatio时,以InitialSurvivorRatio值为准。为保持和其他GC方式统一,建议配置SurvivorRatio。
               <3> 当需要给对象分配内存时,Eden Space空间不够的情况下,如果此对象的大小>=Eden Space一半的大小,就直接在老生代上分配。
               <4> Parallel Scavenge也是server级别,也可以通过-XX:UseParallelGC来强制指定,并行方式时默认的线程数根据CPU核数计算。当CPU核数<=8时,并行的线程数即为CPU核数;当CPU核数>8时,则为3+(CPU核数*5)/8,也可采用-XX:ParallelGCThreads来强制指定线程数。
               <5> 与ParNew收集器的区别:
               (a)关注点不同:
                    <1>Parallel Scavenge目标:达到一个可控制的吞吐量(Throughput)。吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,公式如下:
                    吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾回收时间)。
                    例如:虚拟机共运行100分钟,其中垃圾回收花掉2分钟,那吞吐量就是98%。
                    <2>其他收集器关注点:尽可能地缩短垃圾收集时用户线程的停顿时间。
               (b)适合应用的场景:
                    <1>短停顿适合需要与用户交互的程序,提高用户体验。
                    <2>高吞吐量则可以最高效率地利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。 
               
               <6> 参数设置:          
               (a)控制最大垃圾收集停顿时间的:-XX:MaxGCPauseMillis。允许值是一个大于0的毫秒数。
                注意:将MaxGCPauseMillis参数设置小一点就能使垃圾收集速度变快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的:将新生代调小之后,每次收集速度肯定会提高,但是收集的频率反而会增加。
               譬如:
                    10s执行一次,每次停顿100ms。1分钟=600ms。
                    5s执行一次,每次停顿70ms。1分钟=840ms。
 
               (b)直接设置吞吐量大小的:-XX:GCTimeRatio。允许值是大于0而小于100的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。
               譬如:
                    设置参数为19。那么允许的最大GC时间就占总时间的5%(即:1/(1+19))。默认值为99,就是运行最大1%(即:1/(1+99))的垃圾收集时间。
               
               (c) -XX:+UseAdaptiveSizePolicy参数开关,在运行一段时间后,会根据Minor GC的频率、消耗时间等来动态调整Eden、S0、S1的大小。可通过-XX:UseAdaptiveSizePolicy来固定Eden、S0、S1的大小。打开此开关就不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例等细节参数了。
 
<tenured generation>:
 
          (4)Serial Old收集器:
               是Serial收集器的老生代版本。单线程,Mark-Compact算法。
          
          (5)Parallel Old收集器:
               是Parallel Scavenge收集器的老年代版本。多线程,Mark-Compact算法。这个收集器在1.6之后才开始提供的。
               在注重吞吐量及CPU资源敏感的场合,优先考虑Parallel Scavenge+Parallel Old组合。
 
          (6)CMS(Concurrent Mark Sweep):
运行示意图如下:
 
               是一种以获取最短回收停顿时间为目标的收集器。多线程,使用Mark Sweep算法。
               整个过程包括4个步骤:
               (a)初始标记(CMS initial mark):标记一下GC Roots能直接关联到的对象。
               (b)并发标记(CMS concurrent mark):进行GC Roots Traing的过程。
               (c)重新标记(CMS remark):修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。消耗时间比初始标记长,但是短于并发标记时间。
               (d)并发清除(CMS concurrent sweep)
 
               初始标记和重新标记这2个步骤仍然需要暂停用户线程(Stop The World)。
               最耗时的并发标记和并发清除阶段,收集器线程都可以与用户线程一起工作。
               默认启动的回收线程数:
                    回收线程数:(CPU数量+3)/4。
                    当CPU数量大于4时,并发回收时垃圾收集器线程最多占用不超过25%的CPU资源,但当CPU数小于4时,CMS对用户程序的影响就可能变得很大,导致用户程序的执行速度忽然降低50%。为了解决此问题,虚拟机提供了一种“增量式并发收集器(i-CMS)”,就是让GC线程和用户线程交替运行,不过后续版本并不提倡用户使用此模式。这也是目前CMS的缺点之一,对CPU资源敏感。
               运行示意图如下:
               
               缺点:
               (a)CMS收集器对CPU资源非常敏感。
               (b)CMS运行过程中用户线程仍然在运行,会产生2个该模式下不可避免的问题:
                    (1)CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Faile”失败而导致另一次Full GC的产生。由于CMS并发清除阶段用户线程仍然在运行,自然会产生新的垃圾,而这部分垃圾出现在标记过程之后,当次CMS无法处理掉他们,只要留在下一次GC时再将其清理掉,这就是“浮动垃圾”。
                    (2)CMS收集器无法像其他收集器那样等到老生代几乎被填满时再进行收集,必须预留一部分空间提供并发收集时的程序运行使用。默认设置下:CMS收集器在老生代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果老生代增长较慢,可以使用-XX:CMSInitiatingOccupancyFraction的值来提高触发百分百比,以便降低回收次数获取更好的性能。如果CMS运行期间预留内存无法满足程序需要,就会出现一次“Concurrent Mode Faile”失败,此时虚拟机会启动后备方案,临时启用Serial Old收集器来重新进行老生代的垃圾收集,所以-XX:CMSInitiatingOccupancyFraction参数值不能设置太高。
               (c)由于使用Mark-Sweep算法,所以会产生大量碎片。碎片过多将使大对象的分配很麻烦,而不得不提前进行Full GC。为了解决此问题,CMS收集器提供:-XX:+UseCMSCompactAtFullCollection开关参数。用于在完成Full GC之后,免费附送一个碎片整理过程,内存整理的过程是无法并发的。
          
          (7) G1收集器:
               是垃圾收集器最新的技术产物。优点如下:
               (a)基于“标记-整理”算法,不会产生内存碎片。
               (b)精确控制停顿。
               G1对上述按代划分进行收集的方式更近一步,将整个JAVA堆(新生代,老生代)划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(Garbage First)。
 
总结:
串行处理器:
     --适用情况:数据量比较小(100M左右);单处理器下并且对响应时间无要求的应用。
     --缺点:只能用于小型应用
并行处理器:
     --适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。
     --缺点:垃圾收集过程中应用响应时间可能加长
并发处理器:
     --适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境。

深入理解_JVM内存管理垃圾收集器05