JVM垃圾回收器 没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。 由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本。从不同角度分析垃圾收集器,可以将GC分 为不同的类型常见的GC:Serial、Serial Old、Parallel、Parallel Old、ParNew、CMS、G1等

GC性能指标

吞吐量

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

暂停时间

暂停时间是指一个时间段内应用程序线程暂停,让GC垃圾回收线程执行 。暂停时间优先:意味着尽可能让单次STW时间缩短。

JVM垃圾回收器
两种对比

二者对比

  1. 高吞吐量较好因为这会让应用程序的最终用户感觉只有应用程序线程在“生产性”工作。直觉上,吞吐量越高程序运行越快。
  2. 低暂停时间(低延迟)较好因为从最终用户的角度来看不管是GC还是其他原因导致一个应用被挂起始终是不好的。这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停都可能打断终端用户体验。因此,具有低的较大暂停时间是非常重要的,特别是对于一个交互式应用程序。
  3. 不幸的是”高吞吐量”和”低暂停时间”是一对相互竞争的目标(矛盾)。

现在的标准:在最大吞吐量优先的情况下,降低停顿时间。

经典GC分类

执行机制分类

  1. 串行回收器:Serial 、Serial Old
  2. 并行回收器:ParNew 、Parallel Scavenge、Parallel Old
  3. 并发回收器:CMS、G1

与分代的关系

JVM垃圾回收器
按分代关系划分
  1. 年轻代:Serial、Parallel Scavenge、ParNew
  2. 老年代:Serial Old、Parallel Old、CMS
  3. 整堆收集:G1

它们之间的组合关系

JVM垃圾回收器之间的组合关系
各种GC之间的组合关系

-XX:+PrintCommandLineFlags 命令可以查看当前所有配置信息(包括GC收集器)

1.Serial GC

概述

  1. Serial是最最基本、最悠久的垃圾回收器,JDK1.3之前Serial都是作为新生代唯一的的垃圾回收器。Serial收集器采用复制算法、串行回收和STW机制回收内存。Serial作为HotSpot Client模式下默认的新生代垃圾回收器。
  2. Serial还提供老年区的垃圾回收器-Serial Old,Serial Ola也是采用了STW和串行回收机制,只不过内存回收时采用标记整理算法。
Serial GC工作过程
Serial GC工作过程

参数设置

-XX:+UseSerialGC :设置虚拟机新生代默认使用Serial同时老年代使用Serial Old,下面是JDK8下面默认的GC,根据图片可看出默认使用Parallel GC

Serial 参数设置

使用 -XX:+UseSerialGC 修改之后,重启我们再次查看,可以看到修改成功。

Serial 参数设置

总结

  1. 这种垃圾收集器大家了解,现在已经不用串行的了。而且在限定单核cpu才可以用。现在都不是单核的了。
  2. 对于交互较强的应用而言,这种垃圾收集器是不能接受的。一般在Javaweb应用程序中是不会采用串行垃圾收集器的。

2.ParNew GC

概述

  1. 如果Serial GC是年轻代中的单线程垃圾回收器,那么ParNew就是年轻代中多线程的Serial 收集器版本。Par是Parallel(并行)的缩写,New表示只能处理新生代。
  2. ParNew除了是采用并行回收的方式之外,几乎和Serial没有什么区别,它也是采用复制算法和STW机制实现。
  3. ParNew在很多JVM运行在Server模式下默认新生代的回收器。

参数设置

  1. -XX:+UseParNew :设置JVM新生区默认使用ParNew GC,不影响老年代
  2. -XX:ParallelThreads :限制线程数,默认和CPU数相同。

3.Parallel GC

概述

  1. HotSpot中新生代除了ParNew GC 是基于并行的回收之外,还有Parallel Scavenge 也是基于并行、复制算法、STW机制的垃圾回收器。它相对于ParNew的并行,Parallel Scavenge主要是为了达到一个 可控制的吞吐量,Parallel Scavenge是吞吐量优先的回收器
  2. 高吞吐量可以有效地利用CPU时间,尽快完成运算任务。主要适合在后台进行运算而不需要太多交互的任务。例如订单处理、工资支付、科学计算等场景。
  3. JDK1.6之后,Parallel提供了用于收集老年代垃圾的Parallel Old,替代了原来的Serial Old 收集器。Parallel Old也是采用并行、标记-整理算法、STW机制的收集器。
  4. 在程序吞吐量优先的应用场景中,Parallel 、Parallel Old的组合,在Server模式下内存回收的性能很不错。
  5. JDK8中,默认就使用Parallel 垃圾收集器。

参数设置

  1. -XX:+UseParallelGC:手动指定年轻代使用Parallel 并行收集器,使用Parallel 同时会激活Parallel Old。
  2. -XX:+UseParallelOldGC: 手动指定老年代使用Parallel Old并行收集器, 使用Parallel Old同时会激活Parallel。
  3. -XX:ParallelGCThreads:设置年轻代并行收集器的线程数,CPU<8 时:默认值为CPU数量count,当CPU>8时,默认线程数量=3+(5*count)/8
  4. -XX:MaxGCPauseMills:设置垃圾收集器最大暂停时间(即STW时间)。
  5. -XX:CGTimeRatio:垃圾回收时间占总时间的比例,默认值99。
  6. -XX:+UseAdaptiveSizePolicy:使用Parallel Scavenge收集器自适应调节策略。

4.CMS GC

概述

  1. 在JDK 1.5时期,HotSpot推出」一款在强父互应用中儿乎可认为有划时代意义的垃圾收集器: CMS (Concurrent -Mark - Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义 上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作
  2. CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时
    间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。
  3. CMS采用标记清除算法,并在且也会产生STW(Stop the world)。
  4. CMS作为老年代的垃圾收集器,不能和Parallel Scavenge配合使用

清理过程

CMS GC工作过程
CMS GC工作过程
  1. 初始标记:这个阶段的主要任务是标记出GC Roots能够直接到达的对象,因为只标记可以直接到达的的对象,所以花费时间较短。
  2. 并发标记:从GC Roots的直接关联对象遍历整个对象图,这个时间较长但是不需要暂停用户线程,垃圾回收线程和用户线程并发执行。
  3. 重新标记:修正并发标记期间因为用户线程运作而导致标记产生变动的那一部分对象的标记记录,可能会比初始标记阶段时间长一点,但远小于并发标记所花费的时间。
  4. 并发清除:这个阶段清理标记阶段已经判定死亡的对象,释放内内空间。

特点概括

  1. 由于最消耗时间的并发标记、并发清除阶段都不需要暂停用户线程,所以整体回收过程是低停顿的。
  2. CMS过程中应该还要确保应用程序用户线程有足够的内存空间可用,CMS不能向其他回收器那样等到老年区几乎快满了才开始清理,而必须在超过一定阈值的时候便开始回收,确保CMS工作过程中用户线程仍然可以继续运行,一旦出现CMS工作工程中内存无法满足用户线程运行,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机就会启动后备方案,临时启用Serial Old来清理老年区,但是Serial Old清理停顿时间会很长。
  3. CMS采用的是标记-清除算法,意味着每次清理完内存之后,由于被执行回收的对象内存空间极有可能是不连续的,不可避免地会产生一些垃圾碎片。那么CMS为新对象分配空间时,无法采用指针碰撞技术,只能采用空闲列表执行内存分配
  4. 为什么不采用标记-整理:因为并发清除的阶段,用户线程还在执行,标记整理需要修改对象地址,从而使对象在内存空间连续,一旦修改了用户线程正在使用的对象地址,会导致错误。

优点

  1. 并发收集
  2. 低延迟

缺点

  1. 会产生内存碎片
  2. CMS收集器对CPU资源非常敏感:在并发阶段虽然不会暂停用户线程,但是由于占用了一部分线程,导致用户线程变慢,总吞吐量降低。
  3. CMS无法清除浮动垃圾:如果在并发标记阶段产生新的垃圾对象,CMS无法对这些垃圾进行标记,最终导致这些新产生的垃圾对象没有被回收,从而只能在下一次GC的时候清除这些之前没有被清除的垃圾。

参数设置

  1. -XX:UseConMarkSweepGC:手动指定使用CMS收集器执行内存回收任务。开启这个参数会自动启用ParNew收集器处理新生代垃圾回收。
  2. -XX:CMSInitatingOccupanyFraction:设置堆内存使用的阈值,一旦达到该阈值,便开始进行回收。JDK6之前默认值:68,JDK6及以后默认值:92.
  3. -XX:ParallelCMSThreads:设置CMS线程数量,CMS默认线程数量是 (ParallelThreads+3)/4 ,ParallelThreads 是新生代并行收集器的线程数量。
  4. -XX:+UseCMSCompactAtFullCollection:用于指定在Full GC之后对内存空间进行整理,避免产生内存碎片。由于标记整理算法无法并发执行,所带来的问题就是停顿的时间更加长了。
  5. -XX:CMSFullGCsBeforeCompaction:表示在进行多少次Ful GC之后对内存空间进行整理。

5.G1 GC

G1回收器(Garbage First),将堆内存分成多个区域。GC时它会判断释放哪个区域带来的价值(内存空间)最大,即垃圾优先,对应它的名称 Garbage First。

并行于并发

  1. 并行性:G1在回收期间,可以有多个GC线程,有效利用多核计算能力,此时用户线程STW。
  2. 并发性:G1有和应用线程交替执行的能力,部分工作可以和应用程序同时进行。一般来说不会发生在回收过程中完全阻塞应用线程。

分代收集

  1. 从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代1 然有Eden区和Survivor区。但从堆的结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。
  2. 将堆空间分为若千个区域(Region) , 这些区域中包含了逻辑上的年轻代和老年代。
  3. 和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代。

空间整合

  1. CMS:CMS:“标记- 清除”算法、内存碎片、若干次GC后进行一次碎片整理
  2. G1将内存划分为一个个的region.内存的回收是以region作为基本单位的。 Region之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compac)算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。尤其是当
    Java堆非常大的时候,G1的优势更加明显。

可预测停顿

这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在-一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N亳秒。

  1. 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。
  2. G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region.保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

缺点

  1. 相较于CMS,G1还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint) 还是程序运行时的额外执行负载(Overload) 都要比CMS要高。
  2. 从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。

参数设置

  1. -XX:UseG1GC 手动指定使用G1GC,JDK9默认就是G1
  2. -XX:G1HeapRegionSize:设置每个region的大小,可以是2的N次幂,默认是堆大小的1/32。
  3. -XX:MaxGCPauseMillis:设置期望的最大GC停顿时间。JVM会尽力实现,不保证100%实现,默认是200ms。
  4. -XX:ParallelGCThreads:设置STW工作是GC的线程数值。最多为8
  5. -XX:ConcGCThreads:设置并发标记的线程数量,一般设置为并行GC线程(ParallelThreads)的1/4。
  6. -XX: InitiatingHeapOccupancyPercent:设置触发并发Gc周期的Java堆占用率阈值。超过此值,就触发GC.默认值是45。

分区region

G1 GC分区图示
G1 GC分区图示
  1. 一个region有可能属于Eden,Survivor 或者Old/Tenured内存区域。但是个region只可能属于一个角色。图中的E表示该region属于Eden内存区域,s表示于Survivor内存区域,o表示属于0ld内存区域。图中空白的表示未使用的内存空间。
  2. G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的I 块。主要用于存储大对象,如果超过1 .5个reqion,就放到H。

G1 回收过程

  1. 年轻代GC——Young GC
  2. 老年代并发标记过程——Concurrent Marking
  3. 混合回收:Mixed GC
  4. 如果需要还是有单线程、独占式、高强度的Full GC存在的,这是一种针对GG11评估失败的失败保护机制,即强力回收。
G1 回收流程
G1 回收流程

G1 Rset

G1 在经行Young GC(包括其他GC阶段)时需要扫描每一个Region,这就带来了效率问题,便引入了Remember Set。

  1. 无论G1还是其他垃圾回收器,JVM都是使用Remember Set来避免全局扫描。
  2. 每个Region有对应的Remember Set
  3. 每次Reference类型数据写操作时,都会产生一个Write Barrier 暂时中断操作;然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region (其他收集器:检查老年代对象是否引用了新生代对象) ;
  4. 如果不同,通过CardTable把相关引用信息记录到引用指向对象的所Region对应的Remembered Set中;
  5. 当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set; 就可以保证不进行全局扫描,也不会有遗漏。
Remember Rest
Remember Rest

Young GC

Young GC时,G1首先停止应用程序(Stop-The-World),G1创建回收集,会收集是指需要被回收的内存段的集合。年轻代回收过程包括Eden区和Survivor区的回收。

G1 Young GC 图示
G1 Young GC 图示
  1. 第一阶段,扫描根 : 然后开始如下回收过程: 根是指static变量指向的象,正在执行的方法调用链条上的局部变量等。根引用连同RSet记录的外部引用作为扫描存活对象的入口。
  2. 第二阶段,更新RSet: 处理dirty card queue( 见 备注)中的card,更RSet。 此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用。
  3. 第三阶段,处理RSet : 识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。
  4. 第四阶段,复制对象 : 此阶段,对象树被遍历,Eden区 内存段中存活的对象会被复制到Survivor区中空的内存分段, Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到 01d区中空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代 空间。
  5. 第五阶段,处理引用 : 处理Soft,Weak, Phantom, Final, JNI Weak 等引用。最终Eden空间的数据为空,GC停 止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

并发标记过程

  1. 初始标记阶段:标记廣根节点直接可达的对象。这个阶段是STW的,开且会触发一次年轻代GC。
  2. 根区域扫描(Root Region Scanning) : G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这- -过程必须在young GC之前完成。
  3. 并发标记(Concurrent Marking): 在整个堆中进行并发标记(和应用程序并发执行),此过程可能被youngGC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
  4. 再次标记(Remark): 由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
  5. 独占清理(cleanup, STW): 计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的。这个阶段并不会实际上去做垃圾的收集
  6. 并发清理阶段:识别并清理完全空闲的区域。

混合回收

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个0ld GC,除了回收整个Young Region,还会回收一部分的0ld Region。 这里需要注意:是一部分老年代,而不是全部老年代。可以选择哪些0ld Region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Fu11 GC.

G1 Mixed GC
G1 Mixed GC
  1. 并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次(可以通过- XX:G1MixedGCCountTarget设置)被回收。
  2. 混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区 内存 分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样, 只是回收集多了老年代的内存分段。具体过程请参考上面的年轻代回收过程。
  3. 由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一一个阈值会决定内存分段是否被回收,XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。
  4. 混合回收并不一定要进行8次。 有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。

Full GC

G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(Stop-The-World) ,使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。

要避免Full GC的发生,一旦发生需要进行调整。什么时候会发生Fu1l GC 呢?比如堆内存太小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到full gc,这种情况可以通过增大内存解决。

导致G1Full GC的原因可能有两个:

  1. Evacuation的时候没有足够的to-space来存放晋升的对象
  2. 并发处理过程完成之前空间耗尽。

ZGC

ZGC与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。ZGC目前还处于实验阶段。

《深入理解Java虛拟机》一书中这样定义ZzGC: ZGc收集器是一 -款基于Region内存布局的, (暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的,以低延迟为首要目标的一款垃圾收集器。

ZGC工作过程

  1. 并发标记
  2. 并发预分配
  3. 并发重分配
  4. 并发重映射

7种GC总结

JVM常用七种垃圾回收器对比
JVM常用七种垃圾回收器对比

GC信息参数列表

  1. XX: +PrintGC: 输出Gc日志。类似: -verbose:ge
  2. XX: +Prin tGCDetails: 输出Gc的详细日志
  3. -xx: +PrintGCTimeStamps:输出Gc的时间戳(以基准时间的形式。
  4. -xx: +PrintGcDateStamps输出GC的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)
  5. XX: +PrintHeapAtGC:在进行GC的前后打印出堆的信息
  6. -Xlogge:.. /logs/gc. log日志文件的输出路径

GC 常见日志分析

Young GC

Full GC

标签云

ajax AOP Bootstrap cdn Chevereto CSS Docker Editormd GC Hexo IDEA IPA JavaScript jsDeliver JS樱花特效 JVM Linux markdown Maven MyBatis MyBatis-plus MySQL Pictures Sakura SEO shadowrocket Spring Boot Spring Cloud Spring Cloud Alibaba SpringMVC SSR Thymeleaf V2ray Vue Web WebSocket Wechat Social WordPress Yoast SEO 代理 分页 图床 小幸运 苹果iOS国外账号 苹果IOS账号

JVM垃圾回收器
JVM垃圾回收器
本文最后更新于2020年6月17日,已超过 3 个月没更新!