垃圾收集器

一.
1.
 垃圾收集器
     新生代收集器:Serial  ,   ParNew   ,   Parallel Scavenge
     老年代收集器:Serial Old , Parallel Old , CMS
     整堆收集器:G1

2.
并发垃圾收集和并行垃圾收集
   (1)并行:多条垃圾收集线程并行工作,此时用户线程处于等待状态 如:ParNew,Parallel Scavenge,Parallel Old
   (2)并发:用户线程与垃圾收集线程同时执行(不一定是并行的,可能会交替执行)
   用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上
   如:CMS,G1

3.
(1)Minor GC
         又称 新生代GC ,指发生在新生代的垃圾收集动作
         因为Java对象大多数是朝生夕灭,所以Minor GC非常频繁,回收速度也快

(2)Full GC
         又称 Major GC 老年代GC ,指发生在老年代的GC
         出现 Full GC 经常会伴随至少一次的Minor GC(不是绝对,Parallel Scavenge收集器就可以选择设置Major GC策略)
       Major GC速度一般比Minor GC慢10倍以上;


二.

1.

  Serial收集器
      1.1 Serial(串行)垃圾收集器是最基本,发展历史最悠久的收集器
         特点:
           (1)针对新生代 (2)采用复制算法 (3)单线程收集
                    进行垃圾收集时,必须暂停所有工作线程,直到垃圾收集完成 -> Stop The World
         优点:
           (1)简单高效(与其他收集器的单线程相比);
           (2)对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率

      1.2  设置参数
             “-XX:+UseSerialGC”:添加该参数来显示的使用串行垃圾收集器

      1.3  Stop The World 说明
             JVM在后台自动发起和自动完成的,把用户正常的工作线程全部停掉,即  GC停顿

2.

   ParNew收集器
      PerNew是Serial的多线程版本
      2.1 特点:除了多线程外,其他和Serial收集器一样
            如:Serial可用控制参数手机算法Stop The World内存分配规则回收策略等

            在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。

      2.2 设置参数
            “-XX:+UseConcMarkSweepGC”:指定使用CMS后,会默认使用ParNew作为新生代收集器
            “-XX:+UseParNewGC”:强制指定使用ParNew
            “-XX:ParallelGCThreads”:指定垃圾收集器的线程数量,ParNew默认开启的收集线程与CPU的数量相同

      2.3 为什么只有ParNew能与CMS配合
            CMS是HotSpot在JDK1.退出的第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
            CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作


                 因为Parallel Scavenge和G1都没有使用传统的GC收集器代码框架,而另外独立实现。而其余集中收集器则共用了部分的框架代码。


3.

   Parallel Scavenge收集器

        Parallel Scavenge垃圾收集器因为与吞吐量关系密切也称为吞吐量收集器
     3.1 特点:
         (A)有一些特点与ParNew收集器相似
                      新生代收集器
                   采用复制算法
                   多线程收集
         (B)主要特点是:它的关注点与其他收集器不同
                      CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge目标是达一个可控制的吞吐量(Throughput)
     3.2 应用场景
         (1)高吞吐量为目标即减少垃圾收集时间,让用户代码获得更长的运行时间
         (2)程序运行在多个CPU上,对暂停时间没有特点高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互
                  例如:那些执行批量处理,订单处理,工资支付,科学计算的应用程序

     3.3 设置参数
        Parallel Scavenge 提供两个参数用于精确控制吞吐量
           (1)“-XX:MaxGCPauseMillis” 控制最大垃圾收集停顿时间,大于0的毫秒数
                     MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;因为可能导致垃圾收集发生得更频繁;
           (2)”-XX:GCtimeRatio“
                     设置垃圾收集时间占总时间的比率,0<n<100的整数
           (3)”-XX:+UseAdptiveSizePolicy“
                      开启这个参数后,就不用手工指定一些细节参数,如:
           新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等;
            JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomiscs)

     3.4 吞吐量与收集器关注点说明

           (A)吞吐量(Throughput)
                     CPU用于运行用户代码的时间与CPU总消耗时间的比值
                     即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
                     高吞吐量即减少垃圾收集时间,让用户代码获得更长的运行时间;
           (B)垃圾收集器的目标(关注点)
                 (1)停顿时间 : 停顿时间越短就越适合与用户交互的程序,良好的响应速度能提升用户体验;
                 (2)吞吐量 : 高吞吐量可以高效率的利用CPU时间,尽快完成运算的任务,只要适合在后台计算而不需要太多交互的任务
                 (3)覆盖区 : 在达到前面两个目标的情况下,尽量减少堆的内存空间,可以获得更好的空间局部性

4.

   Serial Old收集器
       Serial Old是Serial收集器的老年代版本
      4.1 特点
        (1)针对老年代 (2)采用标记-整理算法 (3)单线程收集

      4.2 应用场景
          (1)主要用于Client模式
          (2)而在Server模式有两大用途:
                 (A)、在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);
                 (B)、作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用(后面详解);

5.

   Parallel Old收集器
         Parallel Old是Parallel Scavenge收集器的老年代版本 JDK1.6中才开始提供
     5.1 特点:
       (1)针对老年代 (2)采用 标记-整理 算法 (3)多线程收集

   5.2 应用场景
      (1)JDK1.6 及之后用来替代老年代Serial Old收集器
      (2)特别是在Server模式,多CPU的情况下
      (3)这样,在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge Old收集器的”给力“应用组合

   5.3 设置参数
          ”-XX:+UseParallelOldGC“:指定使用Parallel Old收集器


6.

   CMS收集器
        并发标记清理(Concurrent Mark Sweep -> CMS)收集器也称为 并发低停顿收集器 或 低延迟垃圾收集器
     6.1 特点:
      (1)针对老年代 (2)基于 标记-清除 算法(不进行压缩操作,产生内存碎片) (3)以获取最短回收停顿时间为目标 (4)并发收集,低停顿 (5)需要更多的内存

     6.2 应用场景
      (1)与用户交互较多的场景 (2)希望系统停顿时间最短,注重服务的响应速度 (3)以给用户带来较好的体验

     6.3 设置参数
         "-XX:+UseConcMarkSweepGC":指定使用CMS收集器;

     6.4 CMS收集器运作过程
     (1)初始标记(CMS initial mark)
              仅标记一下GC Roots能直接关联到的对象速度很快但需要”Stop The World“
     (2)并发标记(CMS concurrent mark)
              进行GC Roots Tracing的过程
              刚才产生的集合中标记出存活对象,应用程序也在运行
              并不能保证可以标记出所有的存活对象
     (3)重新标记(CMS remark)
               为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,
               需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
               采用多线程并行执行来提升效率;
     (4)并发清除
               回收所有的垃圾对象

               整个过程中耗时最长的并发标记和并发清除都可以与用户线程一起工作;所以总体上说,CMS收集器的内存回收过程与用户线程一起并发执行;

   6.5  CMS的三个明显的缺点

         (1)对CPU资源非常敏感
                  并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低
                  CMS的默认收集线程数量是=(CPU数量+3)/4
                  当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大,不足4个时,影响更大,可能无法接受

7.

G1收集器

G1是JDK7-u4才退出商用的收集器
7.1 特点
(1)并行与并发
         能充分利用多CPU,多核环境下的硬件优势
         可以并行来缩短”Stop The World“停顿时间
         也可以并发让垃圾收集与用户程序同时进行
(2)分代收集,收集范围包括新生代和老年代
         能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配
         能够采用不同方式处理不同时期的对象

         虽然保留分代概念,但Java堆的内存布局有很大差别;
         将整个堆划分为多个大小相等的独立区域(Region);
         新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;
(3)结合多种垃圾手机算法,空间整合,不产生碎片
从整体看,是基于标记-整理算法;从局部看,是基于复制算法;这是一种类似火车算法的实现。
(4)可预测的停顿
G1除了追求低停顿处,还能建立可预测的停顿时间模型
可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒

7.2 应用场景
面向服务端应用,针对具有大内存,多处理器的机器
最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案


在下面的情况时,使用G1可能比CMS好:
(1)、超过50%的Java堆被活动数据占用;
(2)、对象分配频率或年代提升频率变化很大;
(3)、GC停顿时间过长(长于0.5至1秒)。

是否一定采用G1呢?也未必:
如果现在采用的收集器没有出现问题,不用急着去选择G1;
如果应用程序追求低停顿,可以尝试选择G1;
是否代替CMS需要实际场景测试才知道。

7.3 设置参数
"-XX:+UseG1GC":指定使用G1收集器;
"-XX:InitiatingHeapOccupancyPercent":当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;
"-XX:MaxGCPauseMillis":为G1设置暂停时间目标,默认值为200毫秒;
"-XX:G1HeapRegionSize":设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个


7.4 为什么G1收集器可以实现可预测的停顿?

G1可以建立可预测的停顿时间模型,是因为:
可以有计划地避免在Java堆的进行全区域的垃圾收集;
G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;
每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);
这就保证了在有限的时间内可以获取尽可能高的收集效率;

7.5 一个对象被不同区域引用的问题
一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确?
在其他的分代收集器,也存在这样的问题(而G1更突出):
回收新生代也不得不同时扫描老年代?
这样的话会降低Minor GC的效率;

解决方法:
无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描:
每个Region都有一个对应的Remembered Set;
每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作;
然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象);
如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中;

当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set;
就可以保证不进行全局扫描,也不会有遗漏。


7.6 G1收集器运作过程
不计算维护Remembered Set的操作,可以分为4个步骤(与CMS较为相似)。
(A) 初始标记(Initial Marking)
仅标记一下GC Roots能直接关联到的对象;
且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;
需要"Stop The World",但速度很快;
(B) 并发标记(Concurrent Marking)
进行GC Roots Tracing的过程;
刚才产生的集合中标记出存活对象;
耗时较长,但应用程序也在运行;
并不能保证可以标记出所有的存活对象;
(C) 最终标记(Final Marking)
为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;
上一阶段对象的变化记录在线程的Remembered Set Log;
这里把Remembered Set Log合并到Remembered Set中;

需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
采用多线程并行执行来提升效率;
(D) 筛选回收(Live Data Counting and Evacuation)
首先排序各个Region的回收价值和成本;
然后根据用户期望的GC停顿时间来制定回收计划;
最后按计划回收一些价值高的Region中垃圾对象;

回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;
可以并发进行,降低停顿时间,并增加吞吐量;

原文地址:https://www.cnblogs.com/tian666/p/7999105.html