JVM-由浅入深分析经典垃圾收集器

经典垃圾收集器

收集器之间有连线的可以配合使用:
在这里插入图片描述

Serial收集器

单线程工作;在进行GC时,会暂停其他所有工作线程,直到GC结束;

HotSpot运行在Client模式下默认的新生代收集器,简单高效(和其他收集器的单线程相比);对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最少的

新生代使用标记-复制算法,老年代使用标记-整理算法

在这里插入图片描述

ParNew收集器

实质上是Serial收集器的多线程并行版本,除了使用多条线程进行GC之后,其余行为完全一致;

除了Serial收集器之外,目前只有它可以和CMS配合,所有它也是不少运行在Client模式下的首选;

ParNew在单核心处理器的环境中不如Serial,因为其有线程交互的开销;但是随着处理器核心的增加,其对GC时系统资源的高效利用还是很有好处的;

默认开启的线程数和处理器核心数量相同,也可以使用**-XX:ParallelGCThreads**参数来线程线程数;

在这里插入图片描述

Parallel Scavenge收集器

新生代收集器,基于标记-复制算法,多线程并行;更关注于吞吐量;也被称为“吞吐量优先收集器”

停顿时间越短就越适合需要与用户交互或者保证服务响应质量的程序,良好的响应速度可以提高用户体验

高吞吐量可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务

提供了两个参数用于控制吞吐量,控制最大垃圾收集时间 -XX:MaxGCPauseMills 和直接设置吞吐量的大小 -XX:GCTimeRatio

还有一个开关参数 -XX:+UseAdaptiveSizePolicy ,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数已提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为垃圾收集的自适应的调节策略(GC Ergonomics)

SerialOld收集器

Serial的老年版本,单线程,标记整理算法;

在JDK5之前会与Parallel Scavenge收集器搭配使用;

作为CMS收集器发生失败后后备预案,在并发收集器发生Concurrent Mode Failure时使用;

在这里插入图片描述

Parallel Old收集器

Parallel Scavenge的老年代版本,多线程并行,标记整理算法;

注重吞吐量或者处理器资源比较稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old这个组合
在这里插入图片描述

CMS收集器(Concurrent Mark Sweep)

CMS更关注停顿时间;适用于B/S服务端应用;

运作过程:

  1. 初始标记:仅仅标记一下GC Roots直接关联的对象,会STW,很快
  2. 并发标记:从GC Roots直接关联的对象开始遍历整个对象图,可以和用户线程并发
  3. 重新标记:为了修正并发期间,因为并发可达性分析变动的对象,增量更新和原始快照方法;也会STW,稍微比初始标记的时间长一点点
  4. 并发清除:清理掉标记阶段那些死亡的对象,不用移动存活对象,可以和用户线程并发

在这里插入图片描述
CMS也叫“并发低停顿收集器”,还有一些缺点:

  1. 对处理器资源非常敏感(并发程序都这样),并发阶段会占用一部分线程导致应用程序变慢,降低总吞吐量;CMS默认启动的线程数是(处理器核心数量+3)/4;

  2. 无法处理**”浮动垃圾(Floating Garbage)“,有可能出现”Concurrent Mode Failure(并发失败)“进而导致另一次完全STW的Full GC;浮动垃圾是在并发阶段用户线程产生的垃圾,这些垃圾产生在标记结束之后,CMS无法清除他们,只能到下一次GC;由于GC的时候用户线程还在跑,就需要预留足够的内存空间给用户线程**,CMS不能等老年代几乎被装满了再进行GC;

    JDK5默认下,当老年代使用了68%就启动CMS,这可能会引起频繁的GC,可以通过参数**”-XX:CMSInitiatingOccupancyFraction“来设置CMS的触发百分比**;JDK6之后,CMS启动的默认值为92%,但是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现Concurrent Mode Failure(并发失败),这时虚拟机就会使用后备预案:冻结用户进程,改用Serial Old收集器;

  3. CMS基于标记清除算法,那肯定会有空间碎片化问题;

Garbage First收集器

G1收集器开创了收集器面向局部收集的设计思路和基于Region的内存布局形式;

G1是一款面向服务端应用的垃圾收集器,JDK9后取代了Parallel Scavenge加Parallel Old的组合;

G1可以面向堆内存任何部分来组成回收集(Collection Set)进行回收,权衡标准不再是它属于哪个分代,而是哪块内存存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式

G1不再坚持固定大小和固定数量的分代区域划分,而是把连续的java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden区,Survivor区,或者老年代空间。G1对扮演不同角色的Region采用不同的策略去处理;

Region中还有一类特殊的Humongous Region,专门用来存放大对象。大小超过一个Region一半的对象即为大对象;Region的大小可以通过参数 -XX:G1HeapRegionSize 设定,取值范围为1MB-32MB,且为2的N次幂;对于超过了整个Region大小的超级大对象,将会放在N个连续的Humongous Region中

虽然G1仍然保留新生代和老年代的概念,但是新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能够建立可预测的停顿时间模型,是应为它将Region作为单次回收的最小单元,即每次回收的内存空间都是Region大小的整数倍,这样可以有计划的避免整个java堆进行全区域的GC;G1收集器去追踪各个Region里面的垃圾堆积的“价值”大小,价值即为回收所获得空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(-XX:MaxGCPauseMills)优先处理价值收益最大的Region;

在这里插入图片描述
G1收集器还有很多问题需要解决:

  • Region中存在的跨Region引用:在G1上记忆集的应用很复杂,每个Region都要维护自己的记忆集,这些记忆集要记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围中;G1的记忆集在存储结构上本质是一种哈希表,key是别的Region的起始地址,value是一个集合,里面存储的是卡表的索引号;根据经验,G1至少要耗费大概相当于堆10%到20%的额外内存来维持收集器工作

  • 并发标记阶段保证收集器线程和用户线程互不干扰:用户线程改变对象引用关系时,不能打破原本的对象图结构,G1通过**原始快照(SATB)**来实现;

    GC对用户线程的影响还体现在回收过程中新创建对象的内存分配上,程序在跑肯定有新的对象被创建,G1为每一个Region设计了两个名为**TAMS(Top at Mark Start)**的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象都必须在这两个指针位置以;G1默认在这个地址以上的对象是隐式标记过的,即默认他们是存活的,不会回收;

    如果内存回收的速度赶不上内存分配的速度,G1也会被迫冻结用户进程,导致Full GC而产生长时间的STW

  • 建立起可靠的停顿预测模型:G1的停顿预测模型是以**衰减均值(Decaying Average)**来实现的,GC过程中,G1会记录每个Region的回收耗时、每个Region记忆集中的脏卡数量等各个可测量的步骤话费的成本,并分析得出平均值、标准偏差、置信度等统计信息。衰减平均值更准确地代表“最近的”平均状态

如果我们不去计算用户线程运行过程中的动作(如使用写屏障维护记忆集的操作),G1收集器的运作过程为:

  1. 初始标记:仅仅标记GC Roots直接关联的对象,并修改TAMS的值,让下一阶段用户线程并发的时候可以正确分配新对象;
  2. 并发标记:从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,时间很长,但是和用户线程并发;完成后还要重新处理SATB(原始快照)记录下的在并发时引用改变的对象
  3. 最终标记:对用户线程做一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的STAB记录
  4. 筛选回收:复制更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里涉及了存活对象的移动,所以会暂停用户线程,多线程并行处理;

官方给G1的设定是在延迟可控的情况下获得尽可能高的吞吐量;
在这里插入图片描述

收集器的权衡

衡量垃圾收集器的三项最重要的指标是:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency),三者共同组成了一个“不可能三角”。一款优秀的收集器通常最多可以同时达成其中的两项;

如何选择一款适合自己应用的收集器?

  • 应用程序的主要关注点是什么?如果是数据分析、科学计算类的任务,目标是能尽快算出结果,那吞吐量是主要关注点;如果是SLA应用,那停顿时间直接影响服务质量,延迟是主要关注点;如果是客户端或者嵌入式,那垃圾收集器的内存占用是主要关注点;

  • 运行应用的基础设施如何?硬件规格,处理器的数量多少,分配内存的大小,选择的操作系统是什么;

  • 使用JDK的发行商是什么?版本号是多少?该JDK对应了《Java虚拟机规范》的那个版本?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

涛堆堆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值