上面展示了七种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用,下面我将具体的介绍上面的七种垃圾收集器。
不过在介绍之前我要给大家明确一个观点就是:
虽然垃圾收集器的技术在不断进步,但直到现在还没有最好的收集器出现,更加不存在“万能”的收集器,所以我们选择的只是对具体应用最合适的收集器。如果有一种放之四海皆准、任何场景下都适用的完美收集器存在,HotSpot虚拟机完全没必要实现那么多种不同的收集器了。
一、Serial(串行)垃圾收集器
Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器,在JDK1.3.1前是HotSpot新生代收集的唯一选择。
它是一个单线程工作的收集器,它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束(这个过程也称为 Stop The world,俗称STW)。
下图:Serial/Serial Old收集器运行示意图
特点
- 只作用在新生代
- 串行工作有STW现象
- 采用复制算法
- 简单而高效,是Client模式下默认的垃圾收集器
- 对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最小的。
- 对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互(上下文切换)的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
- 可以通过JVM参数-XX:+UseSerialGC设置新生代使用串行垃圾回收器。
二、ParNew收集器
ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。
下图:ParNew/Serial Old收集器运行示意图
在JDK1.7之前,ParNew收集器是新生代的首选,这是为什么呢!
因为除了Serial收集器外,目前只有它能与 CMS 收集器配合工作。
JDK1.5发布时,HotSpot推出了一款真正意义上的可以让垃圾收集器与用户线程同时工作的收集器——CMS收集器。
但是CMS作为一个老年代的垃圾收集器,却是不能与JDK1.4推出来的新生代收集器Parallel Scavenge配合工作,所有就有了老年代使用CMS,新生代就只能选ParNew或者Serial收集器中的一个。
ParNew收集器是激活CMS后的默认新生代收集器。
如果说,ParNew收集器是因为CMS收集器的出现才逐渐让人熟知,那ParNew收集器退出HotSpot虚拟机历史舞台也同样是因为它。
都知道JDK的更新迭代的速度非常快,所以垃圾收集器的技术也是在发生着日新月异的变化,随着更强大的G1垃圾收集器的出现,CMS的时代也成为了历史。
G1是一个面向全堆的收集器,不再需要其他新生代收集器的配合工作。所以自JDK 9开始,ParNew加CMS收集器的组合就不再是官方推荐的服务端模式下的收集器解决方案了。官方希望它能完全被G1所取代,甚至还取消了ParNew加Serial Old以及Serial加CMS这两组收集器组合的支持(其实原本也很少人这样使用),并直接取消了- XX:+UseParNewGC参数,这意味着ParNew和CMS从此只能互相搭配使用,再也没有其他收集器能够和它们配合了。我们也可以理解为从此以后,ParNew合并入CMS,成为它专门处理新生代的组成部分。
特点
- 除了多线程外,其余的行为、特点和Serial收集器一样
- ParNew收集器在单核心处理器的环境中绝对不会有比Serial收集器更好的效果
- 存在线程交互的开销
相关参数:
指定使用CMS后,会默认使用ParNew作为新生代收集: "-XX:+UseConcMarkSweepGC" 强制指定使用ParNew: "-XX:+UseParNewGC" 指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相: "-XX:ParallelGCThreads"
三、Parallel Scavenge收集器
首先Parallel Scavenge收集器是一款新生代收集器它同样是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器。
Parallel Scavenge收集器关注点是吞吐量(如何高效率的利用CPU)。
CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。
下图:Parallel Scavenge/Parallel Old收集器运行示意图
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量
控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数
直接设置吞吐量大小的-XX:GCTimeRatio参数
3.1 -XX:MaxGCPauseMillis参数
该参数设定的值是一个大于 0 的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值。
我们也不要过于极端的把值设置的很小,就以为系统的垃圾收集时间会变得更快,垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的
比如:系统把新生代调得小一些,收集300MB新生代肯定比收集500MB快,但这也直接导致垃圾收集发生得更频繁,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。
3.2 -XX:GCTimeRatio参数
改参数设置地是垃圾收集时间占总时间的比率,0<n<100的整数,相当于吞吐量的倒数。
比如:
用户代码运行时间 99
垃圾收集运行时间 1
吞吐量 = 99 / (1 + 99) = 99%
允许最大占用时间比例 = 1 / (1 + 99) = 1%,即最大比例 1%,n = 1
由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称作“吞吐量优先收集器”。
3.3 值得关注的参数-XX:+UseAdaptiveSizePolicy
开启这个参数后,就不用手工指定一些细节参数,如:
新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等。
JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomiscs) 。
如果我们对于收集器运作不太了解,手工优化存在困难的话,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成是一个很不错的选择。
我们只需设置好内存数据大小(如"-Xmx"设置最大堆),然后使用"-XX:MaxGCPauseMillis"或"-XX:GCTimeRatio"给JVM设置一个优化目标,那些具体细节参数的调节就由JVM自适应完成,这也是Parallel Scavenge收集器与ParNew收集器一个重要区别。
四、Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法,作用于老年代。
运行示意图如上:Serial(串行)垃圾收集器一节中
应用场景
主要用于Client模式;
而在Server模式有两大用途:
在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配)。
作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
五、Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现,作用于老年代。
运行示意图如上:Parallel Scavenge收集器一节中
它是JDK1.6提出,用来代替老年代的Serial Old收集器,特别是在Server模式,多CPU的情况下。
在注重吞吐量以及CPU资源敏感的场景,用Parallel Scavenge加Parallel Old收集器这样"给力"的应用组合,非常有用。
相关设置参数
“-XX:+UseParallelOldGC”:指定使用Parallel Old收集器;
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
感谢您的阅读,十分欢迎并感谢您的关注。
好了,今天的内容到这里就结束了,关注我,我们下期见
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^