Java虚拟机与垃圾回收知识点梳理(三)

Java虚拟机与垃圾回收知识点梳理(一)Java内存区域与HotSpot虚拟机中堆中对象的创建,存储,访问
Java虚拟机与垃圾回收知识点梳理(二)垃圾收集算法
Java虚拟机与垃圾回收知识点梳理(三)HotSpot虚拟机的七种垃圾收集器及它们之间的关系
Java虚拟机与垃圾回收知识点梳理(四)内存分配与回收策略和垃圾收集器实例演示
Java虚拟机与垃圾回收知识点梳理(五)虚拟机类加载机制深入解析

概览

收集算法是内存回收的方法论;垃圾收集器就是内存回收的实践者,也是收集算法的应用。《Java虚拟机规范》中对垃圾收集器应该如何实现并没有做出任何规定。本文以应用最广泛的HotSpot虚拟机为例,主要介绍它的几种垃圾收集器。
如下图是7种垃圾收集器的关系以及它们作用的区域。虚线连接表示不提倡使用
在这里插入图片描述

垃圾收集器语境中的并行和并发

并行和并发都是并发编程中的专业名词,在谈论垃圾收集器的上下文语境中,它们可以理解为:

  • 并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态
  • 并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

工作线程停顿时间和高吞吐量

  • 停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;
  • 而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。

Serial收集器

作用区域:新生代
使用算法:复制
缺点:这个收集器是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
优点:

  • 对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最小的;
  • 对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在用户桌面的应用场景以及近年来流行的部分微服务应用中,分配给虚拟机管理的内存一般来说并不会特别大,收集几十兆甚至一两百兆的新生代(仅仅是指新生代使用的内存,桌面应用甚少超过这个容量),垃圾收集的停顿时间完全可以控制在十几、几十毫秒,最多一百多毫秒以内,只要不是频繁发生收集,这点停顿时间对许多用户来说是完全可以接受的。所以,Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。

ParNew收集器

作用区域:新生代
使用算法:复制
Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数完全一致。
优点:

  • 运行在服务端模式下的HotSpot虚拟机,尤其是JDK 7之前的遗留系统中首选的新生代收集器。
  • 除了Serial收集器外,目前只有它能与CMS收集器配合工作。ParNew收集器是激活CMS后(使用-XX:+UseConcMarkSweepGC选项)的默认新生代收集器。

Parallel Scavenge收集器(吞吐量优先收集器)

作用区域:新生代
使用算法:复制
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。

Parallel Scavenge收集器参数配置

  • 控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数。
  • 直接设置吞吐量大小的-XX:GCTimeRatio参数。
  • -XX:+UseAdaptiveSizePolicy是一个开关参数,当这个参数被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应的调节策略(GC Ergonomics)

Serial Old收集器

作用区域:老年代
使用算法:标记-整理
Serial Old是Serial收集器的老年代版本
特点:

  • 应用在客户端模式下的HotSpot虚拟机
  • 在服务端模式下,它也可能有两种用途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用[插图],另外一种就是作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent ModeFailure时使用

Parallel Old收集器

作用区域:老年代
使用算法:标记-整理
Parallel Old是Parallel Scavenge收集器的老年代版本
在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合。

CMS收集器(并发低停顿收集器)

作用区域:老年代
使用算法:标记-清除
是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求。
运行过程如下图所示
在这里插入图片描述
特点:并发收集、低停顿
缺点:

  • 在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,降低总吞吐量。
  • 在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为**“浮动垃圾”**。同样也是由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此CMS收集器不能像其他收集器那样等待到老年代几乎完全被填满了再进行收集,必须预留一部分空间供并发收集时的程序运作使用
    要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了。
  • 收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC的情况。
    -XX:CMSFullGCsBefore-Compaction(此参数从JDK 9开始废弃),这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表示每次进入Full GC时都进行碎片整理)。

G1收集器(全功能收集器)

G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。
让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。
执行过程如下图所示:
在这里插入图片描述
G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的,换言之,它并非纯粹地追求低延迟,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才能担当起“全功能收集器”的重任与期望。
特点:

  • 面向局部收集的设计思路和基于Region的内存布局形式
  • 面向服务端应用的垃圾收集器
  • 停顿时间可控(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒)
    缺点:
  • 由于Region数量比传统收集器的分代数量明显要多得多,因此G1收集器要比其他的传统垃圾收集器有着更高的内存占用负担。
  • 在并发标记阶段保证收集线程与用户线程互不干扰地运行(用户线程改变对象引用关系时,必须保证其不能打破原本的对象图结构,导致标记结果出现错误);CMS收集器采用增量更新算法实现,而G1收集器则是通过原始快照(SATB)算法来实现的
    和CMS存在相同的问题:垃圾收集对用户线程的影响。在回收过程中新创建对象的内存分配上,程序要继续运行就肯定会持续有新对象被创建,G1为每一个Region设计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。G1收集器默认在这个地址以上的对象是被隐式标记过的,即默认它们是存活的,不纳入回收范围。与CMS中的“Concurrent Mode Failure”失败会导致Full GC类似,如果内存回收的速度赶不上内存分配的速度,G1收集器也要被迫冻结用户线程执行,导致Full GC而产生长时间“Stop The World”。

CMS和G1的优缺点对比

  • G1可以指定最大停顿时间、分Region的内存布局、按收益动态确定回收集
  • CMS作用于老年代,G1适用整个Java堆。
  • CMS的“标记-清除”算法不同,G1从整体来看是基于“标记-整理”算法实现的收集器,但从局部(两个Region之间)上看又是基于“标记-复制”算法实现。不会产生内存空间碎片。
  • G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS要高。
  • 在执行负载的角度上,同样由于两个收集器各自的细节实现特点导致了用户程序运行时的负载会有不同,譬如它们都使用到写屏障,CMS用写后屏障来更新维护卡表;而G1除了使用写后屏障来进行同样的(由于G1的卡表结构复杂,其实是更烦琐的)卡表维护操作外,为了实现原始快照搜索(SATB)算法,还需要使用写前屏障来跟踪并发时的指针变化情况。相比起增量更新算法,原始快照搜索能够减少并发标记和重新标记阶段的消耗,避免CMS那样在最终标记阶段停顿时间过长的缺点,但是在用户程序运行过程中确实会产生由跟踪引用变化带来的额外负担。由于G1对写屏障的复杂操作要比CMS消耗更多的运算资源,所以CMS的写屏障实现是直接的同步操作,而G1就不得不将其实现为类似于消息队列的结构,把写前屏障和写后屏障中要做的事情都放到队列里,然后再异步处理。
  • 小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其优势
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yemuxiaweiliang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值