JVM 垃圾收集器
基本概念
- Young Generation:新生代,hotspot vm中又细分为Eden和两个Survivor(from survivor和to survivor)
- Tenured Generation:老年代
- Minor GC:只GCyoung generation。
- Full GC、Major GC:一个意思,指GC整个heap,包括young generation和tenured generation。
先了解两个重要的垃圾收集性能的度量:
- Throughput(吞吐量) is the percentage of total time not spent in garbage collection considered over long periods of time. Throughput includes time spent in allocation (but tuning for speed of allocation is generally not needed). throughput = 运行用户代码的时间 / (运行用户代码的时间 + 垃圾收集的时间) * 100%。
- Pauses(用户线程停顿时间) are the times when an application appears unresponsive because garbage collection is occurring.因为垃圾收集而导致用户线程暂停的时长。
Hotspot包含的所有收集器:
一、Serial收集器
顾名思义,单线程收集器,“单线程”不仅仅指单个线程或者单个cpu去完成垃圾收集的工作,更重要的是在垃圾收集过程中,所有用户线程都必须暂停,等待整个垃圾收集完成后才恢复执行,官方称为“Stop The World”,新生代young neneration采用停止复制算法,老年代tenured generation采用标记整理算法(如下图)。该收集器是Hotspot在Client模式下的默认新生代young generation的收集器。
Serial收集器运行过程图示:
Serial Collector有以下特点:
- Serial Collector does Minor Collection and Full Collection serially in a stop-the-world fashion. That is, the Java application execution is halted while collection is taking place.
- Serial Collector uses the 3-area (Eden, Survivor Space "From" and Survivor Space "To") algorithm for Minor Collections in the Young Generation.改进版的停止复制算法
- Serial Collector uses the 3-step (Mark, Sweep and Compact) algorithm for Full Collections in the Tenured Generation.标记整理算法
- Serial Collector is the default garbage collector in HotSpot Client 1.5 and higher.
- Serial Collector can be specified using the "-XX:+UseSerialGC" command line option.
优点:
- 相比于其他收集器更简单高效,因为单线程没有线程切换、交互的开销。
缺点:
- Stop The World对于客户端来说可以忍受,但服务端往往不能
二、ParNew收集器
Serial收集器的多线程版本,除了使用多个线程执行垃圾收集这一点之外,其余行为和Serial收集器一致。ParNew收集器是使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可以使用-XX:+UseParNewGC选项来强制指定。默认开启的线程数与CPU的数量相同,如果CPU核数较多,可通过-XX:ParallelGCThreads=<N>来指定线程个数。
ParNew收集器运行过程图示:
三、Parallel Scanvange收集器
也称Throughput Garbage Collector,是一个新生代收集器,它是使用停止复制算法的多线程收集器,这些都和ParNew收集器相同,但Parallel Scanvange收集器的目标是达到一个可控制的Throughput(吞吐量),其提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis=<N>(N为毫秒)以及直接设置吞吐量大小的-XX:GCTimeRatio=<N>(0 < N < 100),
-XX:MaxGCPauseMillis=<N>:N为大于0的毫秒数,收集器尽可能让垃圾收集过程花费少于N ms,你把该参数设小以后,会导致throughput也变小,因为N变小后,JVM会相应地地新生代的大小调小,从而使单次收集时间变短,但是这也就相应地导致触发GC的次数会增加,从而导致整个JVM周期内用在GC上的时间会边长,吞吐量也就变低了。所以说该收集器是以牺牲吞吐量和新生代空间来换取GC停顿时间的缩短的。
-XX:GCTimeRatio=<N>:即User time : GC time,N必须大于0小于100,例如N = 19,则允许的最大GC时间就占总时间的5%(1 / (1 + 19)* 100%)
自适应调节策略:Parallel Scanvange收集器还提供一个开关参数-XX:+UseAdaptiveSizePolicy,当打开该开关参数时,JVM会根据垃圾收集的性能信息动态调整新生代的大小、Eden和Survivor的比例(该比例是通过参数-XX:SurvivorRatio设置的)、晋升老年代对象大小(该值是通过参数-XX:PretenuredSizeThreshold指定的)。
工作过程如下图:
四、Serial Old收集器
是Serial收集器的老年代版本,也是单线程收集器。
工作过程如下图:
五、Parallel Old收集器
是Parallel Scanvange收集器的老年代版本,使用多线程和标记整理算法。
工作过程如下图:
六、CMS收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,采用的是标记清除算法(Mark and Sweep),整个收集过程分四个步骤:
- 初始标记(CMS Initial mark):标记GC Roots能直接关联到的对象。
- 并发标记(CMS concurrent mark):进行GC Roots Tracing的过程。
- 重新标记(CMS remark):修正并发标记阶段因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录。
- 并发清除(CMS concurrent sweep):执行清除过程
耗时的两个阶段并发标记和并发清除都是收集线程和用户线程并发执行的,所以其停顿时间是很短的。
缺点:
- CMS收集器无法回收“浮动垃圾”
由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。
- 内存碎片的问题:
CMS收集器是使用“标记-清除”算法收集的,这会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,往往出现老年代还有很大的剩余内存空间但却找不到足够大的一块来容下一个较大的对象,从而来分配不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认该开关参数就是开着的),用于在Full GC时增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数(默认为0)设置执行多少次不带压缩的Full GC之后,跟着来一次带压缩的Full GC。
工作过程如下图:
七、G1收集器(Garbage-First)
G1是面向服务端应用的垃圾收集器,有如下特点:
- 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
- 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
- 空间整合:与CMS的“标记-清理”算法不同,G1从整体看来是基于“标记-整理”算法实现的收集器,从局部(两个Region之间)上看是基于“复制”算法实现,无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
- 可预测的停顿:这是G1相对于CMS的另外一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器特征了。
在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域
(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验 值),在后台维护一个优先列表,每次根据允许的收集时间,优先回价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限 的时间内获可以获取尽可能高的收集效率。