1. jvm垃圾收集器概览

垃圾收集器是垃圾收集算法的具体实现。本节讨论的收集器基于JDK 1.7 update 14之后。由下图可知,现在都采取这样的分代收集方式。有实现连接的收集器说明可以搭配使用。现在还没有最好的垃圾收集器,他们各有优劣。

2. Serial收集器

最基本、发展历史最悠久的收集器,是Client模式下默认的新生代收集器。运行示意图如下。

  • 缺点: 由于是最早实现的较为简单的垃圾收集器,每次GC时候用户线程有较长的停顿时间。之后更加先进的收集器也在围绕减少暂停时间而努力。只会使用一个CPU或一条收集线程去完成垃圾收集工作。
  • 优点:简单高效,分配几十兆到几百兆的新生代,停顿时间可以控制在几十毫秒最多一百毫秒以内,只要不频繁GC,这点停顿可以接受。没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率。

3. ParNew收集器

是Serial收集器的多线程版本,Server模式下新生代首选的收集器。运行示意图如下:

优点:多线程;可以和CMS(Concurrent Mark Sweep)收集器配合工作。
缺点:单CPU环境中,效率比Serial差得多。

指定方法:-XX:+UseParNewGC或者-XX:+UseConcMarkSweepGC

其他配置:-XX:ParallelGCThreads 参数限制垃圾收集器的线程数

4. Paralle Scanvenge收集器

新生代的收集器,也使用复制算法,也称作吞吐量优先收集器。其他收集器目的是减少暂停时间,而Parallel Scanvenge的目标是达到一个可控制的吞吐量,即CPU用于运行用户代码的时间与CPU总消耗时间的比值。即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。停顿时间侧重的响应速度,而吞吐量则是为了高效率利用CPU时间,尽快完成任务。这主要适合在后台运算而不需要太多交互的任务。

配置参数:

  1. -XX:MaxGCPauseMillis:设置大于0的毫秒数,牺牲吞吐量和缩小新生代空间的办法来尽量保证内存暂停不超过设定值。
  2. -XX:GCTimeRatio:大于0小于100的整数,例如设置成19,则 允许的GC时间占总时间的5%(即1/(1+19))。

4.1 自适应策略

采用Parallel Scanvenge收集器的好处是可以设定参数:
-XX:+UseAdaptiveSiePolicy
启用自适应策略好就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio) 、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数,虚拟机会收集性能监控信息动态调整。

因此只需要设置好几本的内存数据(比如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更加关注停顿时间)或GCTimeRatio(更关注吞吐量)参数给虚拟机设定一个优化目标,剩下的细节参数可以交给JVM处理。

5. Serial Old收集器

Serial收集器的老年代版本,也是一个单线程收集器。Client模式下的虚拟机使用。如果在Server模式下,主要是以下两大用途:

  1. 在JDK1.5之前的版本中与Parallel Scanvenge收集器搭配使用
  2. 作为CMS收集器的后备预案,在并发收集器发生Concurrent Mode Failure时使用。

6. Parallel Old收集器

是Parallel Scanvenge收集器的老年代版本,在JDK1.6之后提供。它的出现很好的能和Parallel Scanvenge进行配合,在追求吞吐量优先的场景和CPU资源敏感的场合可以发挥比较好的效果。

7. CMS(Concurrent Mark Sweep)收集器

CMS收集器是以获取最短回收时间为目标的收集器,也称作并发低停顿收集器。大部分java应用集中在互联网或者B/S系统的服务端上,这类应用尤其重视服务的相应速度,CMS十分符合这样的场景。从名字上看Mark Sweep就知道是基于“标记——清除”算法实现的。运行示意图如下:

运作的整个过程分为4个步骤:

  1. 初始标记(CMS initial mark):仍然需要"Stop the world",标记一个GC root能直接关联到的对象,速度很快。
  2. 并发标记(CMS concurent mark):耗时较长,与用户线程一起进行
  3. 重复标记(CMS remark):仍然需要"Stop the world"
  4. 并发清除(CMS concurrent sweep):耗时较长,与用户线程一起进行
  • 优点:并发收集、少停顿
  • 缺点:仍然会占用用户线程的CPU资源,导致响应时间降低;无法处理浮动垃圾(Floating Garbage),可能出现Concurrent Mode Failure;用于采用标记清除的方式会产生老年代的不连续碎片,所以要进行碎片整理。-XX:CMSFullGCsBeforeCompaction,默认为0,每次full gc都进行碎片整理。

7.1 浮动垃圾和Concurrent Mode Failure

浮动垃圾主要是因为和用户线程并发执行,会导致无法清理干净。而且还要预留一部分空间给用户线程使用,所以CMS不能像其他收集器一样等老年代几乎满了才回收,而是要设定个触发值。如果预留的内存无法满足程序需要,就会Concurrent Mode Failure,从而启动预备方案Serial Old。所以说参数-XX:CMSInitiatingOccupancyFraction设置的太高,容易导致大量Concurrent Mode Failure.

8. G1(garbage first)收集器(最新研究成果,重要!)

JDK1.7之后的最新的收集器,以后可能会替代CMS。主要的特点如下:

  1. 并行与并发
  2. 分代收集
    3.空间整合:基于标记——整理算法实现。使得分配大对象不会因为没连续空间导致提前触发GC。

  3. 可预测的停顿:通过有计划的在JAVA堆中避免进行全区域的垃圾收集,建立了可预测的停顿时间模型。

G1收集器将堆划分为多个大小相等的独立区域(region),虽然保留了新生代和老年代的概念,但是不再是物理隔离了,他们都是一部分。

garbage first的名字是因为G1跟踪各个Region里面的垃圾堆积的价值大小(回收获得的空间大小,和所需时间的经验值)在后台维护一个优先列表,优先回收价值最大的region。

G1实现的时候region之间对象的互相引用,可以通过Remembered Set数据结构记录相关引用信息来避免全堆扫描。

如果不计算维护Remembered Set 的操作,G1收集器的运作大致可划分为以下几个步骤:

  1. 初始标记(Initial Marking):标记GC root能直接关联到的对象
  2. 并发标记(Concurrent Marking): 从GC root开始对堆中进行可达性分析,耗时较长,和用户线程并发执行
  3. 最终标记(Final Marking):修正并发标记阶段由于用户线程运行导致标记产生变动的那一部分。将Remembered Set Logs里面的数据合并到Remembered Set中。
  4. 筛选回收(Live Data Counting and Evacuation):根据各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间来制定回收计划。

9. 垃圾收集器常用参数

10.GC日志

设置参数:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息

例子:
参数设置

-XX:+PrintGCDetails -Xloggc:../logs/gc.log -XX:+PrintGCTimeStamps  

输出:

0.756: [Full GC (System) 0.756: [CMS: 0K->1696K(204800K), 0.0347096 secs] 11488K->1696K(252608K), [CMS Perm : 10328K->10320K(131072K)], 0.0347949 secs] [Times: user=0.06 sys=0.00, real=0.05 secs]  
1.728: [GC 1.728: [ParNew: 38272K->2323K(47808K), 0.0092276 secs] 39968K->4019K(252608K), 0.0093169 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]  
2.642: [GC 2.643: [ParNew: 40595K->3685K(47808K), 0.0075343 secs] 42291K->5381K(252608K), 0.0075972 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]  
4.349: [GC 4.349: [ParNew: 41957K->5024K(47808K), 0.0106558 secs] 43653K->6720K(252608K), 0.0107390 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]  
5.617: [GC 5.617: [ParNew: 43296K->7006K(47808K), 0.0136826 secs] 44992K->8702K(252608K), 0.0137904 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]  
7.429: [GC 7.429: [ParNew: 45278K->6723K(47808K), 0.0251993 secs] 46974K->10551K(252608K), 0.0252421 secs]  

解读方法

5.617(时间戳): [GC(Young GC) 5.617(时间戳): [ParNew(使用ParNew作为年轻代的垃圾回收期): 43296K(年轻代垃圾回收前的大小)->7006K(年轻代垃圾回收以后的大小)(47808K)(年轻代的总大小), 0.0136826 secs(回收时间)] 44992K(堆区垃圾回收前的大小)->8702K(堆区垃圾回收后的大小)(252608K)(堆区总大小), 0.0137904 secs(回收时间)] [Times: user=0.03(Young GC用户耗时) sys=0.00(Young GC系统耗时), real=0.02 secs(Young GC实际耗时)]