【JVM系列三】HotSpot JVM的垃圾回收算法实现-JVM垃圾回收器

引言

上一篇文章《【JVM系列二】深入理解JVM 垃圾回收算法》,详细介绍了JVM对象存活的判定算法引用计数法、可达性分析法--GC Roots 引用链),以及常用的垃圾回收算法标记-清除、复制、标记-整理),本文将分析HotSpot的垃圾回收算法的具体实现。在HotSpot VM中实现这些算法,必须对算法的执行效率进行严格考量,才能保证虚拟机的高效运行。

目前主流的商业虚拟机都是采用“分代收集”的垃圾算法,它主要是根据对象存活周期的不同将堆内存划分为几块。一般是将Java堆分为新生代与老年代,根据不同那个年龄代对象的特点,采用不同的回收算法。在新生代中,每次GC时都会有大部分对象死去,只有少量存活,因此选用复制算法,只需要付出少量存活对象的复制成本就可以完成回收。而老年代中,因为对象存活率高、也没有额外的空间对它进行分配担保,所以选用“标记-清除”或“标记-整理”算法进行GC。

GC类型:

  • Minor GC:针对新生代的GC
  • Major GC:针对老年代的GC
  • Full GC:针对永久代、新生代、老年代三者的GC (JDK 1.7)

一、垃圾收集器

如果说上文介绍的回收算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现

在JVM规范中并没有规定如何实现垃圾收集器,因此不同的厂商、不同版本的虚拟机可以有不同的垃圾收集器实现,并且可以提供参数供用户根据自己的应用特点和要求组合出各个Java堆区域所使用的收集器。

如下,是针对JDK1.7 版本的HotSpot VM的垃圾收集器,这7种作用于不同分代的收集器,其中,如果两个收集器存在连线(蓝色),表示它们可以搭配使用。

注:并不存在哪一种组合是最好的,只有针对某种具体的应用场景,选用最合适的收集器。

新生代:

新生代可使用的GC收集器:Serial Copying、ParNew、ParallelScavenge 等。

  • 分配对象时,Eden空间不足时触发Minor GC
  • 均使用复制算法,原理上是一致的:
  1. 拷贝eden和from中的存活对象到to中;
  2. 部分对象由于某些原因晋升到old中(如to区空间不足、对象存活次数大于15次);
  3. 清空eden、from,from和to交换身份直到下一次GC发生。

老年代:

老年代可使用的GC收集器:CMS、Serial Old、Parallel Old 等,下面在详细分析。

当永久代和老年代触发GC时,除CMS外均会触发Full GC:

  • 首先按照新生代配置的GC方式进行MinorGC;
  • 再按照老年代配置的GC方式对老年代和永久代进行GC;
  • 若JVM估计minor GC后可能会发生晋升失败,则直接采用老年代配置的GC方式对Young、Old、Perm进行GC。

术语介绍:

1、串行(Serial)VS 并行(Parallel)

在单核CPU下并行可能更慢。

2、STW(Stop The World)VS 并发(Concurrent)

  • STW:暂停整个应用线程,只有GC线程在运行,时间可能会很长;
  • 并发:更为复杂,GC可能会抢占应用的CPU。

3、并行(Parallel) VS 并发(Concurrent)

  • 并行:指多条垃圾收集线程并行工作,此时用户线程处于等待状态;
  • 并发:指用户线程和垃圾回收线程同时执行(不一定是并行,有可能是交叉执行),用户程序在继续运行,而垃圾回收线程在另一个 CPU上运行。

注:本文所描述的“用户线程”=“应用线程”。

1.1 Serial 收集器

这是一个新生代的单线程串行收集器。(串行复制

意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。

特性:

  • Serial(串行)、Stop-the-world(暂停应用线程)

适用场景:

  • 单CPU、新生代小、对暂停时间要求不高的应用;
  • 是client级别或32位windows上的默认选择。

对象直接分配在老年代的情况:

  • 对象大小超过eden space大小
  • 大对象(PretenureSizeThreshold)

晋升规则:

  • 经历多次Minor GC仍存活的对象;
  • To Survivor放不下的(满或剩余空间不够)对象直接晋升到老年代。

1.2 ParNew 收集器

ParNew收集器其实就是Serial串行收集器的多线程版本。(并行复制

除了使用多线程进行GC外,其它均与Serial 收集器完全一样,如:控制参数(-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等。

注:虽然ParNew没有多大创新之处,但是它却是许多运行在Server模式下的JVM首选的新生代收集器,主要是除了Serial收集器外,目前只有它可以与老年代的CMS搭配使用

ParNew 收集器是在老年代使用-XX:+UseConcMarkSweepGC参数选项后的默认新生代收集器,当然也可以使用-XX:UseParNewGC参数选项来强制选用它。

特性:

  • Parallel(并行)、Stop-the-world;
  • 可以认为是Serial Copying的多线程版本,各项特征与之基本一致;
  • 可以搭配CMS、Serial Old;
  • 不可搭配Parallel Old。

1.3 Parallel Scavenge 收集器

这也是一个新生代收集器,同样是采用复制算法实现,同时也是并行多线程收集器。

咋描述与ParNew一样?

Parallel Scavenge关注点与其他收集器不同,ParNew、CMS等收集器的关注点是尽可能缩短垃圾收集器时用户线程的停顿时间(缩短STW),而Parallel Scavenge收集器的目的是达到一个可控制的吞吐量(Throughput)。

吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值,即:

Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

STW停顿时间越短越适合需要与用户交互的应用,较快的响应速度能提升用户体验;而提高吞吐量则可以提高CPU的效率,尽快地完成应用的运算任务,主要适合在后台运算而不需要与用户有太多交互的任务
作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间,这就是 GC 的自适应调节策略(GC Ergonomics)。

特性:

  • Parallel(并行)、Stop-the-world
  • 并行线程数默认值:(亦可强制指定线程数(-XX:ParallelGCThreads=4))
  1. CPU核数<=8:=CPU核数
  2. CPU核数>8:=(3+CPU核数*5)/8
  • 会根据Minor GC的频率、时间等动态调整Eden/S0/S1的大小,可取消这一特性(-XX-UseAdaptivesizePolicy)

适用场景:

  • 多CPU、对吞吐量要求高而无需太多交互的应用;
  • 是Server级别(2核CPU2G内存)机器上的默认选择。

对象直接分配在Old的情况:

  • 在TLAB和eden上分配失败,且对象大于eden的一半大小;
  • PretenureSize Threshold参数是无效的。

晋升规则:

  • 经历多次Minor gc仍存活的对象:规则和参数都比Serial Copying复杂;
  • To Survivor放不下的(满或剩余空间不足)对象直接晋升到老年代。

1.4 Serial Old 收集器

Serial Old (Serial MSC)是Serial收集器的老年代版本,单线程,采用 “标记-整理”算法。

该收集器的主要意义是应用在Client模式下的JVM使用。

如下图,与上面介绍的年轻代的Serial收集器搭配使用:

特性:

  • Serial(串行)、Stop-the-world;
  • 使用算法:Mark-Sweep-Compact(标记-整理);
  • 由于是单线程,GC造成的暂停时间可能会很长,可使用-XX:+PrintGCApplicationStoppedTime查看暂停时间。

适用场景:

  • 是client级别或32位windows上的默认选择

1.5 Parallel Old 收集器

Parallel Old(Parallel Cpmpacting) 是 Parallel Scavenge 收集器(吞吐量优先)的老年代版本。多线程,采用“标记-整理/压缩”算法。

该收集器在JDK1.6 后才提供,在此之前,如果新生代选择Parallel Scavenge收集器,则老年代只能选择Serial Old。

如下图,是“吞吐量优先”的GC组合,在注重吞吐量以及CPU资源敏感的场景,可以优先考虑 Parallel Scavenge+Parallel Old 组合,年轻代与年老代的GC都是多线程并行版本。

特性:

  • Parallel(并行)、Stop-the-world;
  • 使用算法:Mark-Compact算法较为复杂。

Parallel Compacting 算法的详细描述:

适用场景:

  • 多核CPU、对暂停时间较敏感的应用,吞吐量优先;
  • 是Server级别(2核CPU 2G内存)机器上的默认选择。

1.6 CMS收集器

CMS (Concurrent Mark Sweep,并发标记清除) 收集器是一种以获取最短回收停顿时间为目标的收集器(最短STW)。

基于“标记-清除”算法实现,也叫并发停顿收集器(Concurrent Low Pause Collector)。

执行过程:

  1. 初始标记(CMS initial mark):标记 GC Roots 引用链能直接关联到的对象,速度很快;
  2. 并发标记(CMS concurrent mark):进行 GC Roots Tracing的过程;
  3. 重新标记(CMS remark):修正并发标记期间的变动部分,停顿时间比初始标记稍长,但远比并发标记短;
  4. 并发清除(CMS concurrent sweep)

其中,并发标记与并发清除是耗时最长的,但是可以与用户线程同时工作(并发),所以,从总体上来看,CMS的内存回收过程是与用户线程一起并发执行的。

特性:

  • Parallel、Concurrent
  • 使用算法:Mark-Sweep算法更为复杂
  • 缩短GC暂停时间,但相当复杂,增加了GC总时间
  • 默认并发线程数=(新生代并行GC线程数+3)/4,也可用-XX:ParallelCMS Threads=2来指定
  • 对Perm Generation也可启用CMS:-XX:+CMSPermGenSweepingEnabled,-XX:+CMSClassUnloadingEnabled

缺点:

  • GC总耗时长
  • 与应用抢占CPU:在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用部分线程(CPU资源)而导致应用程序变慢,总吞吐量降低。其实,面向并发设计的程序都对CPU资源敏感。
  • 无法收集浮动垃圾:可能在“Concurrent Mode Failure”失败时导致另一次Full GC。由于CMS在并发清理阶段,用户线程还在运行,伴随着程序的运行自然会产生新的垃圾,而这部分垃圾称为“浮动垃圾”,它们是出现在标记之后,CMS无法在本次收集中回收它们,只能等待下次GC再处理。
  • 标记-清除算法带来的内存碎片:空间碎片过多,就算老年代总体还有很大空闲内存,在给大对象分配内存时,可能无法找到足够大的连续空间,而不得不提前触发一次Full GC。

因此,CMS提供了一个参数:-XX:+UseCMSCompactAtFullCollection(默认开启),当CMS顶不住要Full GC 时开启内存碎片的合并整理过程,但内存碎片是无法并发的,停顿时间将变长;

另外还有一个参数:-XX:CMSFullGCsBeforeCompaction,用于执行多少次不压缩的Full GC后,跟着来一次压缩整理(默认值为0,表示每次进入Full GC时都进行碎片整理)。

适用场景:

  • STW暂停时间短,对追求最快响应速度的应用,尤其是互联网应用很适用。

面试可能会经常问CMS,CMS的优缺点、过程描述。

待续...

参考:《深入理解Java虚拟机》

史上最强Tomcat8性能优化

阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路

B2B电商平台--ChinaPay银联电子支付功能

学会Zookeeper分布式锁,让面试官对你刮目相看

SpringCloud电商秒杀微服务-Redisson分布式锁方案

查看更多好文,进入公众号--撩我--往期精彩

一只 有深度 有灵魂 的公众号0.0

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值