JVM垃圾回收

最近上班中发现,发现我们没有良好的利用机器的性能,并且由于是老项目,竟在新的机器上短时间内部署不起来,因此造就了我们两个方向的发展。一是容器化部署,不再依赖环境;第二个是让目前的几台机器能够承担更多的压力。

同样的,我发现我们目前几个机器的的使用率很低,并且接口的响应很慢,因此需要去分析GC,调整GC参数,来使得响应更好,机器使用率更好,能够扛住更大的吞吐。

回收算法

一、标记-清除

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

存在两个缺点:

  • 效率:标记和清除两个过程的效率都不高
  • 空间:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能导致以后需要分配大对象时,无法找到足够的连续内存而提前触发垃圾回收

二、复制

将可用内存按容量划分为大小相等的两块,每次只使用一块。当这一块使用完了,则将活着的对象拷贝到另一块上。

缺点:

  • 内存缩小为了原来的一半

三、标记-整理

先标记出所有需要回收的对象,然后将存活的对象都向一端移动,清除掉端边界以外的内存。

四、分代回收

根据对象存活周期的不同将内存划分为几块。一般Java堆分为新生代和老年代,可以根据各个年代的特点采用最适当的回收算法。

垃圾回收器

一、Serial

单线程收集器。在整个垃圾回收过程中都需要暂停其他所有工作线程。

Serial

二、ParNew

ParNew是Serial的多线程版本。主要差一点是在回收过程中充分利用多CPU优势,回收过程中也多线程。

ParNew

三、Parallel Scavenge

Paralle Scavenge的目标是达到一个可控制的吞吐量。主要适合在后台运算而不需要太多交互的任务。

吞吐量是CPU用于运行用户代码的时间与CPU总消耗时间的比值。吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾回收时间)。

提供了两个参数控制吞吐:MaxGCPauseMillis,最大垃圾回收停顿时间;GCTimeRatio:设置吞吐大小。GC停顿时间的缩小是以牺牲吞吐量和新生代空间来换取的,及通过将新生代设置得更小,来似的GC停顿时间更短,但GC次数会增加。UseAdaptiveSizePolicy开关,可以使得JVM根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间和最大的吞吐量(GC自适应的调节策略,这也是Parallel Scavenge和ParNew的最大区别)。

Parallel Scavenge无法和CMS配合工作。

四、Serial Old

Serial Old就是Serial的老年代版本。

五、Parallel Old

Parallel Old就是Parallel Scavenge的老年代版本。

Parallel Old

六、CMS

CMS是以获取最短回收停顿时间为目标的垃圾回收器。常用在Web场景,需要响应客户请求的场景。CMS的特点大部分回收过程都可以与用户线程一起并发执行。

整理上CMS分为四个步骤:

  • 初始标记(STW):仅标记GC Roots能直接关联对象,速度很快
  • 并发标记:对GC Roots的Tracing
  • 重新标记(STW):修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,STW时间一般比初始标记稍长,但远比并发标记的时间短
  • 并发清除

CMS存在3个明显的缺点:

  • 对CPU资源非常敏感。在CMS并发阶段,会占用一部分线程,会导致应用程序变慢,总吞吐降低。CMS默认线程数是(CPU数量 + 3)/ 4。
  • 无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败导致另一次Full GC产生。在垃圾回收过程中,用户线程依然在运行,因此会不断产生垃圾,这部分在当前这次CMS无法处理,只能等下次GC清理。CMSInitiatingOccupancyFraction值,代表当老年代达到多少比例时就触发CMS回收,预留内存满足程序运行的需要。如果出现了“Concurrent Mode Failure”失败,则通过后备预案——临时启动Srial Old来重新进行老年代垃圾回收。
  • 基于“标记-清除”算法实现,意味着收集结束时有大量空间碎片产生。CMS提供参数UseCMSCompactAtFullCollection开关(默认开启),在CMS回收器扛不住要进行Full GC时开启内存碎片的合并整理过程,内存整理过程无法并发,STW停顿时间变长。CMSFullGCsBeforeCompaction参数,用于设置执行多少次不压缩的Full GC后,进行一次带压缩的Full GC。(此处的Full GC使用的是类似Serial Old的算法,单线程对全堆以及Metaspace空间进行回收

CMS

七、G1

G1是面向服务端应用的垃圾回收器。将整个Java堆分为多个大小相等的独立区域(Region),虽然保留新生代和老年代的概念,新生代和老年代不再是物理隔离的,都是一部分Region(不需要连续)的集合。

G1的特点:

  • 并行与并发
  • 分代收集:虽然G1可以独立管理整个GC堆,但依然能够采用不同的方式处理新创建的对象和已存活一段时间、熬过多次GC的旧对象以获取更好的回收效果。
  • 空间整合:G1整体上是基于标记-整理算法实现的,从局部(两个Region之间)上看是居于复制算法实现的。因此在G1运行期间不会产生内存碎片。
  • 可预测的停顿:建立可预测的停顿时间模型,能够明确指定在一个长度为M毫秒的时间段内,消耗在垃圾回收上的时间不超过N毫秒。

整体上G1分为四个阶段:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

G1

CMS与G1如何选择

暂无详细资料推荐,目前比较稳妥方式是,使用CMS解决问题,当CMS无法解决时,采用G1(建议经过验证)。

在实践中发现,对于大内存的情况下,ParNew+CMS的组合已经无法提供良好的实时响应了,因此一般Web类项目都建议以小内存+多机器的方式实现实时响应。