评估GC的性能指标

  • 吞吐量:运行用户代码的时间占运行时间的比例
    • 总运行时间:程序的运行时间 + 内存回收的时间
  • 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例
  • 暂停时间:执行垃圾收集时,程序的工作现场被暂停的时间。
  • 收集频率:性对于应用程序的执行,收集操作发生的频率
  • 内存占用:Java堆区所占的内存大小

垃圾收集器与垃圾分代之间的关系

image.png

垃圾收集器的组合关系

image.png

如何查看默认的垃圾收集器

  • -XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
  • 使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID

Serial回收器:串行回收

  • serial收集器作为HotSpot中Client模式下的默认新生代垃圾收集器。
  • Serial收集器采用复制算法、串行回收和“stop the world”机制的方式执行内存回收。
  • 除了年轻代之外,Serial收集器还提供用于执行老年代垃圾收集的Serial Old收集器。
  • Serial Old收集器同样也采用串行回收和“stop the world”机制,只不过内存回收算法使用的是标记-压缩算法
    • Serial Old 是运行在Client模式下默认的老年代的垃圾回收器
    • Serial Old在Server模式下主要有两个用途
      • 与新生代的parallel Scavenge配合使用
      • 作为老年代CMS收集器的后备垃圾收集方案
  • 这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有工作线程,知道它收集结束(Stop The World)。
  • 在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以知道年轻代和老年代都使用串行收集器

ParNew回收器:并行回收

  • ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。ParNew收集器在年轻代中同样也是采用复制算法、“Stop The World”机制
  • ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器
  • 在程序中,开发人员可以通过选项“-XX:UseParNewGC”手动指定使用ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。
  • -XX:ParallelGCThreads现在线程数量,默认开启和CPU数据相同的线程数。

Parallel Scavenge回收器:吞吐量优先

HotSpot的年轻代中除了拥有ParNew收集器时基于并行回收的以外,Parallel Scavenge收集器同样也采用了复制算法、并行回收和“Stop The World”机制。

  • 和ParNew收集器不同,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量,他也被称为吞吐量优先的垃圾收集器。
  • 自适应调节策略也是Parallel Scavenge与ParNew一个重要区别。
  • Parallel Old收集器,用来替代老年代的Serial Old收集器。
  • Parallel Old收集器采用标记-压缩算法,但同样也是基于并行回收和“Stop The World”机制
  • -XX:+UseParallelGC手动指定年轻代使用Parallel并行收集器执行内存回收任务。
  • -XX:+UseParallelOldGC手动指定老年代都是使用并行回收收集器。
    • 分别适用于新生代和考年代。默认jdk8是开启的
    • 上面上个参数,默认开启一个,另一个也会被开启
  • -XX:ParallelGCThreads设置年轻代并行收集器的线程数。一般最好与CPu数量相等,以避免过多线程数影响垃圾收集性能。
  • -XX:MaxGCPauseMillis设置垃圾收集器最大停顿时间(即STW的时间)。单位是毫秒
    • 为了尽可能地把停顿时间控制在MaxGCPauseMills以内,收集在工作时会调整Java堆大小或者其他一些参数
    • 该参数谨慎使用
  • -XX:GCTimeRatio垃圾收集时间占总时间的比例(1/(N + 1))。
    • 取值范围(0,100)。默认值99,也就是垃圾回收时间不超过1%
    • 与前一个-XX:MaxGCPauseMills参数有一定矛盾性。暂停时间越长,Ratio参数就容易超过设定的比例

CMS回收器:低延迟

  • CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合于用户交互的程序,良好的响应速度提升用户体验。
    • 目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
  • CMS的垃圾收集算法采用标记-清除算法,并且也会“Stop The World”

CMS工作原理

image.png

  1. 初始标记(Initial-Mark)阶段:这个阶段中,程序中所有的工作线程都将会因为“Stop-The-World”机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记处GC Roots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于之间关联对象比较小,所以这里的速度非常快。
  2. 并发标记(Concurrent-Mark)阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
  3. 重新标记(Remark)阶段:由于在并发阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的挺短时间通常会比出师表及阶段稍长一些,但也远比并发标记阶段的时间短。
  4. 并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

    CMS优点

  • 并发收集
  • 低延迟

    CMS缺点

  1. 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
  2. CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
  3. CMS收集器无法处理浮动垃圾。可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被即时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。

    CMS相关指令

  4. -XX:+UseConcMarkSweepGC手动指定使用CMS收集器执行内存回收任务。

    1. 开启该参数后会自动将-XX:+UseParaNewGC打开
  5. -XX:CMSInitiatingOccupanyFraction设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收
    1. JDK5及以前版本的默认值为68,即当老年代的空间使用流程达到68%时,会执行一次CMS回收。JDK6及以上版本默认值为92%
    2. 如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以明显地改善应用程序性能。反之,如果应用程序使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低Full GC执行次数
  6. -XX:+UseCMSCompactAtFullCollection用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的的问题就是停顿时间变得更长了。
  7. -XX:CMSFullGCBeforeCompaction设置在执行多少次Full GC后对内存空间进行压缩整理。
  8. -XX:ParallelCMSThreads设置CMS的线程数量。
    1. CMS默认启动的线程数是(ParallelGCThreads + 3)/ 4
  9. -XX:+CMSScavengeBeforeRemark 在执行remark之前先进行一次young GC,目的减少年轻代对老年代的无效引用,减少remark时的开销
  10. -XX:+CMSInitiaingOccupancyOnly 如果不指定,仅设定CMSInitiaingOccupancyFraction,jvm仅在第一次使用设定值,后续则自动调整导致设定值无效。
  11. -XX:CMSMaxAbortablePrecleanTime=n 在CMS的preclean阶段开始前,等待minor gc的最大时间。
  12. -XX:+CMSClassUnloadingEnabled 表示开启 CMS 对永久代的垃圾回收(或元空间),避免由于永久代空间耗尽带来 Full GC

    参考文章

    https://www.jianshu.com/p/86e358afdf17

G1回收器:区域分代化

官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当其“全功能收集器”的重任与期望。

因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的Region来表示Eden、幸存者1区、幸存者0区,老年代等。

G1 GC有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的手机时间,优先回收价值最大的Region。

G1回收器的特点(优势)

  • 并发与并行
    • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW。
    • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会再整个回收阶段发生完全阻塞应用程序的情况。
  • 分代收集
    • 从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden区和Survivor区。但从堆结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。
    • 将堆空间分为若干区域(Region),这些区域中包含了逻辑上的年轻代和老年代。
    • 和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代;
  • 空间整合
    • CMS:“标记-清除”算法、内存碎片、若干次GC后进行一次碎片整理
    • G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看做是标记-压缩(Mark-Compact)算法,两种算法都可以避免内存碎片。
  • 可预测的停顿时间模型
    • 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
    • G1跟踪各个region里面的垃圾堆积的价值大小(回收获得空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的手机时间,优先回收价值最大的region。保证了G1收集器在优先的时间内可以获得尽可能高的手机效率。
    • 相比CMS GC,G1未必能够做到CMS在最好情况下的延时停顿,但是最差情况要好很多。

G1回收器的参数设置

  1. -XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务
  2. -XX:G1HeapRegionSize 设置每个region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000
  3. -XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms
  4. -XX:ParallelGCThread 设置STW工作线程数的值。最多设置为8
  5. -XX:ConcGCThreads 设置并发标记的线程。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右
  6. -XX:InitiatingHeapOccupancyPercent 设置触发并发GC周期的Java堆占用阈值。超过此值,就触发GC。默认是45。

    G1回收器垃圾回收过程

  7. 年轻代GC(Young GC),独占式

  8. 老年代并发标记过程(Concurrent Marking)
  9. 混合回收(Mixed GC)
  10. (如果需要,单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供一种失败保护机制,即强力回收)

Remembered Set

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

  1. 无论G1还是其他分代收集器,JVM都是使用Remembered Set 来避免全局扫描
  2. 每个region都有一个对应的Remembered Set
  3. 每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作
  4. 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的region(其他收集器:检查老年代对象是否引用了新生代对象)
  5. 如果不同,通过CardTable吧相关引用信息记录到引用锁指向对象的所在region对应的Remembered set中
  6. 当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set:就可以保证不进行全局扫描,也不会遗漏