Java垃圾回收中垃圾回收器(二)

垃圾回收器

(1)GC分类和GC性能指标

分类:

按线程数分串行GC(只有一个cpu执行GC)、并行GC
按工作模式分并行式GC(与应用程序线程交替工作)、独占式GC(暂停应用程序线程)
碎片处理压缩式GC(回收碎片)、非压缩式GC(不回收GC)
工作内存年轻代GC、老年代GC

性能指标:

主要是在意这三个性能指标

  • 吞吐量:运行用户线程/总时间

  • 暂停时间:用户线程暂停时间

  • 垃圾收集开销:垃圾GC时间/总时间

  • 内存占用:Java堆区所在的内存大小

(2)GC发展史

串行Serial GC--------->Parallel GC 和Concurrent Mark Sweep GC(CMS)------>G1-JDK11--->Epsilion GC 和ZGC

Serial Old GC Parallel Old GC

  • 1999年随JDK1.3.1一起来的是串行方式的serialGc,它是第一款GC。ParNew垃圾收集器是Serial收集器的多线程版本

  • 2002年2月26日,Parallel GC和Concurrent Mark Sweep GC跟随JDK1.4.2一起发布·

  • Parallel GC在JDK6之后成为HotSpot默认GC。

  • 2012年,在JDK1.7u4版本中,G1可用

  • 2017年,JDK9中G1变成默认的垃圾收集器,以替代CMS

  • 2018年3月,JDK10中G1垃圾回收器的并行完整垃圾回收,实现并行性来改善最坏情况下的延迟。

  • 2018年9月,JDK11发布。引入Epsilon 垃圾回收器,又被称为 "No-Op(无操作)“ 回收器。同时,引入ZGC:可伸缩的低延迟垃圾回收器(Experimental)

  • 2019年3月,JDK12发布。增强G1,自动返回未用堆内存给操作系统。同时,引入Shenandoah GC:低停顿时间的GC(Experimental)。·2019年9月,JDK13发布。增强zGC,自动返回未用堆内存给操作系统。

  • 2020年3月,JDK14发布。删除cMs垃圾回收器。扩展zGC在macos和Windows上的应用

7种经典GC:

  • 串行回收器:Serial、Serial old

  • 并行回收器:ParNew、Parallel Scavenge、Parallel old

  • 并发回收器:CMS、G1

  • 两个收集器间有连线,表明它们可以搭配使用:Serial/Serial old、Serial/CMS、ParNew/Serial old、ParNew/CMS、Parallel Scavenge/Serial 0ld、Parallel Scavenge/Parallel 01d、G1;

  • 其中Serial o1d作为CMs出现"Concurrent Mode Failure"失败的后备预案。

  • (红色虚线)由于维护和兼容性测试的成本,在JDK 8时将Serial+CMS、ParNew+Serial old这两个组合声明为废弃(JEP173),并在JDK9中完全取消了这些组合的支持(JEP214),即:移除。

  • (绿色虚线)JDK14中:弃用Paralle1 Scavenge和Serialold GC组合(JEP366)

  • (青色虚线)JDK14中:删除CMs垃圾回收器(JEP363)

两个小问题:

  • 为什么Parallel Scanvenge GC 不能和CMS GC一起合作:

PS GC底层实现的框架不同

  • 为什么存在多个GC

没有在任何情况下都适用的GC,选择的只是对具体应用最合适的GC

(3)Serial GC

Serial GC

串行收集器(只有一个cpu进行GC)、进行年轻代的垃圾收集、采用Copying垃圾清除算法、串行进行垃圾回收、存在STW

Serial Old GC

串行收集器、进行老年代的垃圾收集、Mark-Compact垃圾清除算法、串行进行垃圾回收、存在STW

  • client:默认的老年代GC

  • Server模式下:与新生代Parallel Scavenge配合 作为CMS的后备GC收集

(4)ParNew GC

parallel的缩写,并行回收(其他与Serial GC没有区别)

ParNew和Serial Old配合使用:

  • 新生代:回收频繁、用并行比较高效

  • 老年代:回收少、用串行节省资源

(5)Parallel 回收器

吞吐量优先

Parallel Scavenge

并行收集器、进行年轻代的垃圾收集、Copying垃圾清除算法、STW

与ParNew不同:

  • Parallel Scavenge是为了达到一个可控制的吞吐量

  • 自适应调节策略(可以动态调整内存)

Parallel Old GC

并行收集器、进行老年代的垃圾收集、Mark-Compact、STW

JDK8中默认GC:Parallel+Parallel Old

(6) CMS回收器

基本信息:

concurrent Mark Sweep GC 主打低延迟(尽可能缩短GC的停顿时间)

并发收集器(让垃圾收集线程和用户线程同时工作)、Mark-Sweep垃圾清除算法、进行老年代的垃圾收集、STW

只能和Serial GC 和ParNew GC配合使用,不能和Parallel Scavenge GC配合

收集过程:

  • 初始标记:只标记与GC Roots直接关联的对象STW

  • 并发标记:标记所有存活对象(并发标记)、因为有用户线程存在所以不能再内存填满了再进行回收(当堆空间内存使用率达到一定的阈值就开始进行回收)

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

  • 重新标记:由于上一步并行,所以可能存在新的未被标记的对象。(STW

  • 并发清除:清除已死亡对象、释放空间,但是存在内存碎片(并行

CMS为什么不使用标记整理算法?

因为当并发清除的时候,用Compact整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适合“stop the world” 这种场景下使用

优缺点:

  • 优点:低延迟+并发收集

  • 缺点:产生内存碎片+对cpu资源敏感(在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低)+无法处理浮动垃圾(在并发标记阶段产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。)

GC选择:

  • 如果你想要最小化地使用内存和并行开销,请选Serial GC;

  • 如果你想要最大化应用程序的吞吐量,请选Parallel GC;

  • 如果你想要最小化GC的中断或停顿时间,请选CMs GC

JDK后续版本中CMS的变化

  • JDK9新特性:CMS被标记为deprecate了

  • JDK14新特性:删除CMS

(7) G1回收器

基本信息:

在延迟可控的情况下获得尽可能高的吞吐量

即可以收集新生代的垃圾也可以收集老年代垃圾,并行回收器分区收集算法(分Region)

主要是适应不断扩大的内存和不断增加的处理器数量(引入G1原因

G1跟踪各个Region里面的垃圾堆积的价值大小,优先回收价值最大的Region(侧重点在于回收垃圾最大量的region,所以叫做garbage first 垃圾优先

G1优劣势:

优势:

  • 兼具并行和并发:

    • 并行:多个GC同时工作

    • 并发:GC和应用程序交替工作

  • 分区收集

  • 空间整合:Region支架的清除是Copying算法,但是整体上的清除算法是Mark-Compact,没有产生碎片

  • 可预测的停顿时间模型 soft real-time:能够指定在一个长度为M毫秒的时间片段内消耗在GC的时间不能超过N毫秒

缺点:

G1在为了GC产生的内存占用、程序运行时的额外执行负载都比CMS高。从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。

G1参数设置

  • -XX:+UseG1GC:手动指定使用G1垃圾收集器执行内存回收任务

  • -XX:G1HeapRegionSize设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。

  • -XX:MaxGCPauseMillis 设置期望达到的最大Gc停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms

  • -XX:+ParallelGcThread 设置STW工作线程数的值。最多设置为8

  • -XX:ConcGCThreads 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGcThreads)的1/4左右。

  • -XX:InitiatingHeapoccupancyPercent 设置触发并发Gc周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。

G1适用场景:

  • 服务端应用 大内存 多处理器

  • 低GC延迟

  • G1可能比CMS好的情况:

    • 超过50%的Java堆被活动数据占用

    • GC停顿时间过长

    • 对象分配频率变化很大

Region:

使用G1收集器时,将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。可以通过XX:G1HeapRegionsize设定。所有的Region大小相同,且在JVM生命周期内不会被改变

主要是E、S、O、H(H区主要是用于放置大对象)

H区存在的必要性:之前分配大对象是直接分配到老年代、但是如果是一个短期存在的大对象就会对GC有负面影响,所以开辟出一个H区放置大对象,如果一个H区装不下,就会寻找连续的H区存储

G1回收过程:

Remembered Set:

出现原因:一个对象可能会被不同区域引用,即一个Region中的对象可能会被任意Region中的对象引用,那判断这个对象是否存活,就需要进行全局扫描进行确定(那记忆集就是用来解决这个全局扫描的问题

解决:给每个Region都分配一个Rset,每个Rset记录了当前Region中的对象的引用情况(每次就直接扫描Rset就可)

G1回收主要是三个环节:

  • 年轻代GC

并行,STW,当Eden区用尽时开启YoungGC,主要是将E区的存活对象移至S区,S区的对象符合要求的移至老年代

  • 老年代并发标记过程

当堆内存使用达到一定值时(默认是45%),触发老年代 concurrent marking

1.初始标记阶段:标记从根节点直接可达的对象。这个阶段是STW的,并且会触发一次年轻代GC。

2.根区域扫描(Root Region Scanning):G1 Gc扫描survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在youngGC之前完成。

3.并发标记(Concurrent Marking):在整个堆中进行并发标记(和应用程序并发执行),此过程可能被youngGC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。

4.再次标记(Remark):由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning(SATB)。

5.独占清理(cleanup,STW):计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是sTw的。这个阶段并不会实际上去做垃圾的收集

6.并发清理阶段:识别并清理完全空闲的区域。

  • 混合回收:

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

(8)垃圾回收器总结

新生代都是复制Copying算法;老年代除了CMS都是Mark-Compact

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值