05-jvm-GC算法-实际应用

680 阅读9分钟

垃圾收集器

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现

性能度量指标

  • 吞吐量:表示系统减去系统回收时间占总时间的比率,比如,系统运行了100秒,垃圾回收占用了1秒,那么吞吐量就是(100-1)/100 = 99%

  • 垃圾回收消耗:和吞吐量相反,垃圾回收器消耗指的是垃圾回收器耗时与系统运行总时间的比值

  • 停顿时间:指垃圾回收器运行的时候,系统停顿的时间

  • 回收频率:指垃圾回收器多长时间运行一次,一般来说,对于固定的应用而言,垃圾回收器的频率应该越低越好,通常增加堆空间可以有效的降低垃圾回收发生的频率,但是可能会增加回收产生的停顿时间

  • 反应时间:当一个内存对象被标记为垃圾对象后到这个对象真正回收所产生的时间

    根据这几个指标,我们可以知道,垃圾回收性能好的表现是:吞吐量高,垃圾回收消耗低,停顿时间少,回收频率低,反应时间快。但是,并没有这么完美的性能表现,这几个指标有些是互斥的,比如要降低回收频率,就要扩大空间,但是就会增加停顿时间;同样要想反应时间快,就必须要提高回收频率。所以,这些性能的追求就是一个博弈平衡的过程,我们可以根据我们追求的某一方面来进行调优,比如,对于客户端应用而言,应该尽可能降低其停顿时间,给用户良好的使用体验,为此,可以牺牲垃圾回收的吞吐量;对服务端程序来说,可能会更加关注吞吐量。

按执行机制划分Java有四种类型的垃圾回收器

image

  1. 串行垃圾回收器(Serial Garbage Collector)
  2. 并行垃圾回收器(Parallel Garbage Collector)
  3. 并发标记扫描垃圾回收器(CMS Garbage Collector)
  4. G1垃圾回收器(G1 Garbage Collector)
  • 每种类型都有各自的优势和劣势,在很大程度上有所不同,可以给我们提供完全不同的应用程序性能
  • 我们编程的时候,可以通过向jvn传递参数,选择正确的垃圾回收器的类型
Serial收集器 (串行垃圾回收器)

image
image

  • 串行收集器是最古老、最稳定以及效率高的垃圾回收器。

  • 使用单线程进行垃圾回收,独占式垃圾回收

  • 串行垃圾回收器通过持有应用程序所有的线程进行工作。它为单线程环境设计,只使用一个单独的线程进行垃圾回收,通过冻结所有应用程序线程进行工作,所以可能不适合服务器环境

  • 它最适合的是简单的命令行程序(单CPU、新生代空间较小及对暂停时间要求不是非常高的应用)。是client级别默认的GC方式。

  • 新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)

    参数控制: -XX:+UseSerialGC 串行收集器

ParNew收集器 (串行垃圾回收器)

image

image

  • ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩

  • 只有他能与CMS收集器配合工作。

  • 参数控制:

    -XX:+UseParNewGC ParNew收集器 -XX:ParallelGCThreads 限制线程数量 -XX:+UseParNewGC:新生代使用并行收集器,老年代使用串行回收器。 -XX:+UseConcMarkSweepGC:新生代使用并行回收器,老年代使用CMS

  • 并行收集器工作时的线程数量可以使用 -XX:ParallelGCThreads 参数指定。一般最好与CPU数量相当,避免过多的线程数,影响垃圾收集性能

  • 在默认情况下,当CPU数量小于8个时,ParallelGCThreads 的值等于 CPU 数量;当 CPU 数量大于8个时,ParallelGCThreads 的值等于 3+[(5*CPU_Count)/8]

Parallel Scavenge 收集器 (并行垃圾回收器)
  • 并行垃圾回收器也被称之为:吞吐量收集器(throughput collector)

  • 他是jvm默认的垃圾回收器

  • 与串行垃圾回收器不同的是它使用多线程进行垃圾回收,使用的是复制算法

  • 相似的是执行垃圾回收的时候,他也会冻结所有的应用程序线程

  • 使用与CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式。

  • 参数控制

    -XX:+UseParallelGC 新生代使用并行回收收集器,老年代使用串行回收器。 -XX:ParallelGCThreads=4 指定线程数

Parallel Old 收集器并行垃圾回收器)

image

  • Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供

  • 参数控制

    -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行

并发标记扫描垃圾回收器
  • 使用多线程扫描内存,标记需要清理的实例,并且清理已经标记的实例。
  • 并发标记扫描垃圾回收器只会在以下两种情况持有应用程序的所有线程
    • 标记的引用对象在永久(Tenured)区域
    • 在进行垃圾回收的时候,堆内存的数据被并发的改变。

相比并行垃圾回收器,并发标记扫描垃圾回收器使用更多的CPU来确保程序的吞吐量。如果我们可以为了更好的程序性能分配更多的CPU,那么并发标记上扫描垃圾回收器是更好的选择相比并发垃圾回收器。

通过JVM参数 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。

CMS收集器

image

  • CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器
  • 从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的
  • 它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:
    • 初始标记(CMS initial mark)
    • 并发标记(CMS concurrent mark)
    • 重新标记(CMS remark)
    • 并发清除(CMS concurrent sweep)
  • 其中初始标记、重新标记这两个步骤仍然需要“Stop The World”
  • 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,
  • 并发标记阶段就是进行GC Roots Tracing的过程
  • 重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短
  • 由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew)
优点:

并发收集、低停顿

缺点:

产生大量空间碎片、并发阶段会降低吞吐量

参数控制:
  1. -XX:+UseConcMarkSweepGC 使用CMS收集器
  2. -XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
  3. -XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
  4. -XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
G1收集器
  • G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器
  • 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
  • 可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
  • 使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
以上的垃圾回收器需要组合使用

image

常用的收集器组合
组合 新生代GC策略 老年老代GC策略 说明
组合1 Serial Serial Old Serial和Serial Old都是单线程进行GC,特点就是GC时暂停所有应用线程。
组合2 Serial CMS+Serial Old CMS(Concurrent Mark Sweep)是并发GC,实现GC线程和应用线程并发工作,不需要暂停所有应用线程。另外,当CMS进行GC失败时,会自动使用Serial Old策略进行GC
组合3 ParNew CMS 使用 -XX:+UseParNewGC选项来开启。ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量。可以使用-XX:ParallelGCThreads选项指定GC的线程数。如果指定了选项 -XX:+UseConcMarkSweepGC选项,则新生代默认使用ParNew GC策略。
组合4 ParNew Serial Old 使用 -XX:+UseParNewGC选项来开启。新生代使用ParNew GC策略,年老代默认使用Serial Old GC策略。
组合5 Parallel Scavenge Serial Old Parallel Scavenge策略主要是关注一个可控的吞吐量:应用程序运行时间 / (应用程序运行时间 + GC时间),可见这会使得CPU的利用率尽可能的高,适用于后台持久运行的应用程序,而不适用于交互较多的应用程序。
组合6 Parallel Scavenge Parallel Old Parallel Old是Serial Old的并行版本
组合7 G1GC G1GC -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #开启; -XX:MaxGCPauseMillis=50 #暂停时间目标; -XX:GCPauseIntervalMillis=200 #暂停间隔目标; -XX:+G1YoungGenSize=512m #年轻代大小; -XX:SurvivorRatio=6 #幸存区比例