版本:JDK1.7 U14

概述

1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。

作用

  • 哪些内存需要回收
  • 什么时候回收
  • 如何回收

为什么要懂

当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集称为系统达到更高并发量的瓶颈时,就需要对这些“自动化”的技术实施必要的监控和调节。

Serial

特点

  • 最基本、发展历史最悠久的收集器
  • JDK1.3.1之前,虚拟机新生代收集的唯一选择
  • 单线程(Stop The World)
    • 只会使用一个CPU或一条收集线程去完成垃圾收集工作
    • 进行垃圾收集时,必须暂停其它所有的工作线程,直至结束
      • 虚拟机后台自动发起、完成,用户不可见
  • 收集算法
    • 新生代采取复制算法
    • 老年代采取标记-整理算法
  • 虚拟机运行在Client模式下的默认新生代收集器
  • 优点
    • 简单高效
      • 对于限定单个CPU的环境,没有线程交互的开销

运行示意图

ParNew

特点

  • Serial GC的多线程版本
  • 与Serial GC一致
    • 控制参数
    • 收集算法
    • Stop The World
    • 对象分配规则
    • 回收策略
  • 虚拟机运行在Server模式下默认新生代收集器
    • 除Serial收集器外,只有它能与CMS收集器配合工作

运行示意图

兼容性

CMS作为老年代的收集器,却无法与JDK 1.4.0中已经存在的新生代收集器Paralled Scavenge配合工作。所以在JDK1.5中使用CMS时,新生代只能选择Paralled Scavenge或者Serial。Parallel Scavenge没有使用原本HotSpot其它GC通用的那个GC框架,所以不能跟使用了那个框架的CMS搭配使用。就这么简单,不是什么算法/技术上的不兼容,纯粹是人为造成的。Parallel Scavenge和ParNew抽象来说是同一思路的GC,而后者可以跟CMS搭配使用。这样就够了。

与Serial对比

ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百的保证可以超越Serial收集器。当然,随着可以使用的CPU的数量的增加,它对于GC使系统资源的有效利用还是很有好处的。

超线程技术是一个很好的提升核心利用率的东西,将闲置处理资源充分调用起来,增强核心并行运算性能,在操作系统中一颗物理CPU能当作多颗CPU来使用。

好处:

  • 有效提升CPU利用率
  • 改善计算机的性能
  • 提高系统可靠性

随着核心数目增多超线程的作用就越弱。

需要CPU、主板、操作系统、应用软件支持。

默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

并发:两件以上的事件在同一时间间隔发生。同一实体

并行:两件以上的事件在同一时刻发生。不同实体

Parallel Scavenge

特点

  • 新生代收集器
  • 复制算法
  • 并行的多线程收集器
  • “吞吐量优先”收集器
    • 目标是达到一个可控制的吞吐量(Throughput)

运行示意图

吞吐量

  • CPU用于运行用户代码的时间与CPU总消耗的时间的比值
  • 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
  • 虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%
  • 高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务

参数

  • 控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis
    • 大于0的毫秒数
    • 收集器将尽可能的保证内存回收花费的时间不超过设定值。
    • GC停顿时间缩短是牺牲吞吐量和新生代空间来换取的。
  • 设置吞吐量大小:-XX:GCTimeRatio
    • 大于0且小于100的整数,默认99
      • 允许最大1%(1/(1+99))的垃圾收集时间
    • 垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。
    • 值为19,那允许最大GC时间就占总时间的5%(1/(1+19))
  • GC自适应的调节策略(GC Ergonomics):-XX:UseAdaptiveSizePolicy
    • 开关参数
    • 不需要手工指定下列细节参数
      • 新生代的大小:-Xmn
      • Eden与Survivor区的比例:-XX:SurvivorRatio
      • 晋升老年代对象的年龄:-XX:PretenureSizeThreshold

与ParNew的区别

Parallel Scavenge的自适应调节策略。

建议

如果对收集器运作原理不太了解,手工优化困难,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成。只需要设置基本的内存数据,如:设置最大堆:-Xmx,然后使用MaxGCpauseMillis参数GCTimeRatio参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。

Serial Old

特点

  • Serial的老年代版本
  • 单线程收集器
  • “标记-整理”算法
  • 主要意义是Client模式下的虚拟机使用
  • Server模式下的用途
    • JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用
    • 作为CMS收集器的后备预案

需要说明一下,Parallel Scavenge收集器架构中本身有PS MarkSweep收集器来进行老年代收集,并非直接使用了Serial Old收集器,但是这个PS MarkSweep收集器与Serial Old的实现非常接近,所以在官方的许多资料中都是直接以Serial Old代替PS MarkSweep进行讲解,这里笔者也采用这种方式。

运行示意图

Parallel Old

特点

  • Parallel Scavenge老年代版本
  • 多线程
  • “标记-整理”算法
  • JDK1.6中提供
  • 在注重吞吐量以及CPU资源敏感的场合,优先考虑Parallel Scavenge加Parallel Old收集器

JDK1.6之前,新生代的Parallel Scavenge一直处于比较尴尬的位置。

如果新生代选择了Parallel Scavenge,老年代只能选择Serial Old(PS MarkSweep)。由于Serial Old收集器在服务端应用性能上的“拖累”,使用Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量还不一定有ParNew加CMS组合好。

CMS

全称为:Concurrent Mark Sweep

特点

  • 获取最短回收停顿时间为目标
  • “标记-清除”算法

运作过程

  • 初始标记(CMS initial mark)
    • Stop The World
    • 标记GC Roots能直接关联到的对象
    • 速度很快
  • 并发标记(CMS concurrent mark)
    • Stop The World
    • 进行GC Roots Tracing的过程
  • 重新标记(CMS remark)
    • 修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
    • 停顿时间一般会比初始标记阶段稍长一些,远比并发标记的时间短
  • 并发清除(CMS concurrent sweep)
  • 重置线程

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

运行示意图

优点

  • 并发收集
  • 低停顿
    • Sun公司的官方文档中也称为并发低停顿收集器(Concurrent Low Pause Collector)

缺点

  • 对CPU资源非常敏感

在并发阶段,虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数:(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程占用不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个时,CMS对用户程序的影响就可能变得很大。如果本来CPU负载就比较大,还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%。

i-CMS:虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器变种,就是在并发标记、清理的时候让GC线程、用户线程交替运行,尽量减少GC线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得少一些。但效果很一般,已经被声明为“deprecated”,即不再提倡用户使用

  • 无法处理浮动垃圾(Floating Garbage)

浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行时自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。

可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。

-XX:CMSInitiatingOccupancyFraction设置的太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。

由于垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间,因此CMS收集器不能等到老年代几乎完全被填满了再进行收集。

在JDK1.5的默认设置下,CMS手机器当老年代使用了68%的空间后就会被激活。可以通过-XX:CMSInitiatingOccupancyFraction来提高触发百分比,以便降低回收次数从而获取更好的性能。

在JDK1.6中,CMS手机的启动阀值已经提升至92%。

要是CMS运行期间预留的内存无法满足程序的需要,就会出现“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾手机,这样停顿时间就很长了。

  • 产生大量空间碎片

CMS是一款基于“标记-清除”算法实现的收集器,意味着收集结束时会有大量空间碎片产生,空间碎片过多时,将会给打对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

CMS收集器提供开关参数-XX:UseCMSCompactAtFullCollection,用于在CMS收集器要进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。

虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)

G1

概述

  • Garbage-First,当今收集器技术发展的最前沿成果之一,面向服务端应用的垃圾收集器,目标是干掉CMS收集器。
  • “标记-整理”
  • 将整个Java堆划分为多个大小相等的独立区域(Region),新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合

特点

  • 并行与并发

G1能充分利用多CPU、多核环境下的硬件优势,缩短Stop-The-World时间,通过并发的方式让Java程序继续执行。

  • 分代收集

独立管理整个GC堆,采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获得更好的收集效果。

  • 空间整合

从整体来看是基于“标记-整理”算法实现,从局部(两个Region之间)看是基于“复制”算法实现的。

意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。有利于程序长时间运行。

  • 可预测的停顿

建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

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

这种使用Region划分内存空间以及有优先级的区域回收方式,保证了在有限的时间内可以获取尽可能高的收集效率。

问题及解答

把Java堆分为多个Region后,垃圾收集是否就真的能以Region为单位进行了?

G1中每个Region多有一个与之对应的Remembered Set,虚拟机发现程序在堆Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代概念里就是检查是否老年代中的对象引用了新生代中的对象)。

是:通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中,当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

运作过程

  • 初始标记(Initial Marking)

标记GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,需要停顿线程,耗时很短

  • 并发标记(Concurrent Marking)

从GC Root开始对堆中对象进行可达性分析,找出存活的对象,耗时较长,可与用户程序并发执行

  • 最终标记(Final Marking)

修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,需要线程停顿,但是可并行执行。

  • 筛选回收(Live Data Counting and Evacuation)

首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,可与用户程序并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。

运行示意图

总结

对比

垃圾回收器 Generation JDK 算法 特点
Serial Young 1.3 复制 简单高效,用户线程停顿时间长
ParNew Young 1.4 复制 多线程版Serial,适合多CPU
Parallel Scavenge Young 1.4 复制 吞吐量优先,自适应调节策略
Serial Old Tenured 1.5 标记-整理 老年代版Serial
Parallel Old Tenured 1.6 标记-整理 搭配Parallel Scavenge,吞吐量优先
CMS Tenured 1.5 标记-清除 短时间停顿,响应速度极快
G1 ALL 1.7 标记-整理 并行和并发, 可预测停顿

名字

  • Serial:串行化收集器、DefNew(默认新生代收集器)
  • ParNew:并行新生代收集器(Parallel New)
  • Parallel Scavenge:PSYoungGen、PSScavenge、并行清除收集器
  • Serial Old:串行化老年代收集器
  • Parallel Old:PSMarkSweep、PSCompact、并行老年代收集器
  • CMS:Concurrent Mark Sweep、并发标记扫描、Concurrent Low Pause Collector、并发低停顿收集器
  • G1:Garbage First、垃圾优先收集器

资料

Our Collectors

Parallel Scavenge收集器为何无法与CMS同时使用?

ParNew和PSYoungGen和DefNew是一个东西么—R大的回答

Java GC总结