本文已被 Github 仓库收录 https://github.com/silently9527/JavaCore
前言
前几天写了一篇《JVM 性能调优实战:让你的 IntelliJ Idea 纵享丝滑》,其中有对 GC 垃圾回收器的抉择尝试,本篇咱们就来具体的看看 JVM 中常见的垃圾回收器有哪些以及每个垃圾回收器的特点,这也是面试的时候常常被问的内容
JVM 堆内存概览
在聊垃圾回收器之前,咱们先来看看 JVM 堆内存的区域划分是怎么样的,看下图
- 因为虚拟机应用的垃圾回收算法是分代收集算法,所以堆内存被分为了新生代和老年代
- 新生代应用的垃圾回收算法是复制算法,所以新生代又被分为了 Eden 和 Survivor;空间大小比例默认为 8:2
- Survivor 又被分为了 S0、S1,这两个的空间大小比例为 1:1
内存调配以及垃圾回收
- 对象优先在 Eden 区进行调配,如果 Eden 区满了之后会触发一次 Minor GC
- Minor GC 之后从 Eden 存活下来的对象将会被挪动到 S0 区域,当 S0 内存满了之后又会被触发一次 Minor GC,S0 区存活下来的对象会被挪动到 S1 区,S0 区闲暇;S1 满了之后在 Minor GC,存活下来的再次挪动到 S0 区,S1 区闲暇,这样反反复复 GC,每 GC 一次,对象的年龄就涨一岁,默认达到 15 岁之后就会进入老年代,对于晋身到老年代的年龄阈值能够通过参数
-XX:MaxTenuringThreshold
设置 - 在 Minor GC 之后须要的发送晋身到老年代的对象没有空间安置,那么就会触发 Full GC (这步非相对,视垃圾回收器决定)
Minor GC 和 Full GC 的区别:Minor GC 是指产生在新生代的垃圾收集行为,因为对象优先在 Eden 区调配,并且很多对象都是朝生夕死,所以触发的频率绝对较高;因为采纳的复制算法,所以个别回收速度十分快。Full GC 是指产生在老年代的垃圾收集行为,Full GC 的速度个别会比 Minor GC 慢 10 倍以上;所以不能让 JVM 频繁的产生 Full GC
为了可能更好的适应不同程序的内存状况,JVM 也不肯定要求必须达到年龄 15 岁能力晋身到老年代,如果在 Survivor 区中雷同年龄的所有对象大小总和大于 Survivor 区空间的一半,年龄大于或者等于这个年龄的对象将会间接进入到老年代
Full GC 触发条件
- 代码中调用
System.gc()
- 老年代空间有余 / 满了
- 长久区空间有余 / 满了
留神:大对象会间接在老年代分配内存,能够通过参数
-XX:PretenureSizeThreshold
管制对象的大小,通常遇到的大对象是很长的字符串或者数组,如果调配了一大群大对象只是长期应用,生命很短暂,那么就会频繁的产生 Full GC,然而此时的新生代的空间还有闲暇;写代码的时候,这种状况应该防止,特地是在创立数组的时候要当心
空间担保
在新生代产生 Minor GC 的时候,JVM 会先查看老年代中可调配的间断空间是否大于新生代所有对象的总和,如果大于,那么本次 Minor GC 就能够平安的执行;如果不大于,那么 JVM 会先去查看参数 HandlePromotionFailure
设置值是否容许空间担保失败,如果容许,JVM 会持续查看老年代可调配的间断空间是否大于历次降职到老年代对象的均匀大小,如果大于,只管这次 Minor GC 是有危险的,JVM 也会尝试一次 Minor GC;如果不容许担保失败,那么 JVM 间接进行 Full GC
尽管担保有可能会失败,导致饶一圈能力进行 GC,然而还是倡议把这个参数关上,能够防止 JVM 频繁的 Full GC
垃圾回收器概览
从上图能够看出:
- 新生代能够应用的垃圾回收器:Serial、ParNew、Parallel Scavenge
- 老年代能够实用的垃圾回收器:CMS、Serial Old、Parallel Old
- G1 回收器实用于新生代和老年代
- 相互之间有连线的示意能够配合应用
CMS 和 Serial Old 同为老年代回收器,为何互相会有连线呢?
Serial 收集器
这是个单线程收集器,倒退历史最悠久的收集器,当它在进行垃圾收集工作的时候,其余线程都必须暂停直到垃圾收集完结(Stop The World)。
尽管 Serial 收集器存在 Stop The World 的问题,然而在并行能力较弱的单 CPU 环境下往往体现优于其余收集器;因为它简略而高效,没有多余的线程交互开销;Serial 对于运行在 Client 模式下的虚拟机来说是个很好的抉择
应用 -XX:+UseSerialGC
参数能够设置新生代应用这个 Serial 收集器
ParNew 收集器
ParNew 收集器是 Serial 收集器的多线程版本;除了应用了多线程进行垃圾收集以外,其余的都和 Serial 统一;它默认开始的线程数与 CPU 的核数雷同,能够通过参数 -XX:ParallelGCThreads
来设置线程数。
从下面的图能够看出,可能与 CMS 配合应用的收集器,除了 Serial 以外,就只剩下 ParNew,所以 ParNew 通常是运行在 Server 模式下的首选新生代垃圾收集器
应用 -XX:+UseParNewGC
参数能够设置新生代应用这个并行回收器
Parallel Scavenge 收集器
Parallel Scavenge 收集器仍然是个采纳复制算法的多线程新生代收集器,它与其余的收集器的不同之处在于它次要关怀的是吞吐量,而其余的收集器关注的是尽可能的缩小用户线程的等待时间(缩短 Stop The World 的工夫)。吞吐量 = 用户线程执行工夫 /(用户线程执行工夫 + 垃圾收集工夫),虚拟机总共运行 100 分钟,其中垃圾收集破费工夫 1 分钟,那么吞吐量就是 99%
进展工夫越短适宜须要和用户进行交互的程序,良好的响应可能晋升用户的体验。而高效的吞吐量能够充沛的利用 CPU 工夫,尽快的实现计算工作,所以 Parallel Scavenge 收集器实用于后盾计算型工作程序。
-XX:MaxGCPauseMillis
能够管制垃圾收集的最大暂停工夫,须要留神不要认为把这个工夫设置的很小就能够缩小垃圾收集暂用的工夫,这可能会导致产生频繁的 GC,反而升高了吞吐量
-XX:GCTimeRatio
设置吞吐量大小,参数是取值范畴 0 -100 的整数,也就是垃圾收集占用的工夫,默认是 99,那么垃圾收集占用的最大工夫 1%
-XX:+UseAdaptiveSizePolicy
如果关上这个参数,就不须要用户手动的管制新生代大小,降职老年代年龄等参数,JVM 会开启 GC 自适应调节策略
Serial Old 收集器
Serial Old 收集器也是个单线程收集器,实用于老年代,应用的是标记 - 整顿算法,能够配合 Serial 收集器在 Client 模式下应用。
它能够作为 CMS 收集器的后备预案,如果 CMS 呈现 Concurrent Mode Failure,则 SerialOld 将作为后备收集器。(前面 CMS 具体阐明)
Parallel Old 收集器
Parallel Old 收集器能够配合 Parallel Scavenge 收集器一起应用达到“吞吐量优先”,它次要是针对老年代的收集器,应用的是标记 - 整顿算法。在重视吞吐量的工作中能够优先思考应用这个组合
-XX:+UseParallelOldGc
设置老年代应用该回收器。
XX:+ParallelGCThreads
设置垃圾收集时的线程数量。
CMS 收集器
CMS 收集器是一种以获取最短回收进展工夫为指标的收集器,在互联网网站、B/ S 架构的中罕用的收集器就是 CMS,因为零碎进展的工夫最短,给用户带来较好的体验。
-XX:+UseConcMarkSweepGC
设置老年代应用该回收器。
-XX:ConcGCThreads
设置并发线程数量。
CMS 采纳的是标记 - 革除算法,次要分为了 4 个步骤:
- 初始化标记
- 并发标记
- 从新标记
- 并发革除
初始化标记和从新标记这两个步骤仍然会产生 Stop The World,初始化标记只是标记 GC Root 可能间接关联到的对象,速度较快,并发标记可能和用户线程并发执行;从新标记是为了修改在并发标记的过程中用户线程产生的垃圾,这个工夫比初始化标记稍长,比并发标记短很多。整个过程请看下图
长处
- CMS 是一款优良的收集器,它的次要长处:并发收集、低进展,因而 CMS 收集器也被称为并发低进展收集器(Concurrent Low Pause Collector)。
毛病
- CMS 收集器对 CPU 资源十分敏感。在并发阶段,它尽管不会导致用户线程进展,但会因为占用了一部分线程(或者说 CPU 资源)而导致应用程序变慢,总吞吐量会升高。CMS 默认启动的回收线程数是(CPU 数量 +3)/4,也就是当 CPU 在 4 个以上时,并发回收时垃圾收集线程不少于 25% 的 CPU 资源,并且随着 CPU 数量的减少而降落。然而当 CPU 有余 4 个时(比方 2 个),CMS 对用户程序的影响就可能变得很大,如果原本 CPU 负载就比拟大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度突然升高了 50%,其实也让人无奈承受。
- 无奈解决浮动垃圾。因为 CMS 并发清理阶段用户线程还在运行着,随同程序运行天然就还会有新的垃圾一直产生。这一部分垃圾呈现在标记过程之后,CMS 无奈再当次收集中解决掉它们,只好留待下一次 GC 时再清理掉。这一部分垃圾就被称为“浮动垃圾”。也是因为在垃圾收集阶段用户线程还须要运行,那也就还须要预留有足够的内存空间给用户线程应用,因而 CMS 收集器不能像其余收集器那样等到老年代简直齐全被填满了再进行收集,回收阀值能够通过参数
-XX:CMSInitiatingoccupancyFraction
来设置;如果回收阀值设置的太大,在 CMS 运行期间如果调配大的对象找不到足够的空间就会呈现“Concurrent Mode Failure”失败,这时候会长期启动 SerialOld GC 来从新进行老年代的收集,这样的话进展的工夫就会加长。 - 标记 - 革除算法导致的空间碎片 CMS 是一款基于“标记 - 革除”算法实现的收集器,这意味着收集完结时会有大量空间碎片产生。空间碎片过多时,将会给大对象调配带来很大麻烦,往往呈现老年代空间残余,但无奈找到足够大间断空间来调配以后对象。为了解决这个问题 CMS 提供了一个参数
-XX:+UseCMSCompactAtFullCollecion
,如果启用,在 Full GC 的时候开启内存碎片整顿合并过程,因为内存碎片整顿的过程无奈并行执行,所以进展的工夫会加长。思考到每次 FullGC 都要进行内存碎片合并不是很适合,所以 CMS 又提供了另一个参数-XX:CMSFullGCsBeforeCompaction
来管制执行多少次不带碎片整顿的 FullGC 之后,来一次带碎片整顿 GC
G1 收集器
G1 是一款面向服务端利用的垃圾回收器。
- 并行与并发:与 CMS 相似,充沛里用多核 CPU 的劣势,G1 依然能够不暂停用户线程执行垃圾收集工作
- 分代收集:分代的概念仍然在 G1 保留,过后它不须要和其余垃圾收集器配合应用,能够独立治理整个堆内存
- 空间的整合:G1 整体上采纳的是标记 - 整顿算法,从部分(Region)采纳的是复制算法,这两种算法都意味着 G1 不须要进行内存碎片整顿
- 可预测的进展:可能让用户指定在工夫片段内,耗费在垃圾收集的工夫不超过多长时间。
Region
尽管在 G1 中仍然保留了新生代和老年代的概念,然而采纳的是一种齐全不同的形式来组织堆内存,它把整个堆内存宰割成了很多大小雷同的区域(Region),并且新生代和老年代在物理上也不是间断的内存区域,请看下图:
每个 Region 被标记了 E、S、O 和 H,其中 H 是以往算法中没有的,它代表 Humongous,这示意这些 Region 存储的是巨型对象,当新建对象大小超过 Region 大小一半时,间接在新的一个或多个间断 Region 中调配,并标记为 H。Region 区域的内存大小能够通过 -XX:G1HeapRegionSize
参数指定,大小区间只能是 2 的幂次方,如:1M、2M、4M、8M
G1 的 GC 模式
- 新生代 GC:与其余新生代收集器相似,对象优先在 eden region 调配,如果 eden region 内存不足就会触发新生代的 GC,把存活的对象安置在 survivor region,或者降职到 old region
- 混合 GC:当越来越多的对象降职到了 old region,当老年代的内存使用率达到某个阈值就会触发混合 GC,能够通过参数
-XX:InitiatingHeapOccupancyPercent
设置阈值百分比,此参数与 CMS 中-XX:CMSInitiatingoccupancyFraction
的性能相似;混合 GC 会回收新生代和 局部老年代内存,留神是局部老年代而不是全副老年代;G1 会跟踪每个 Region 中的垃圾回收价值,在用户指定的垃圾收集工夫内优先回收价值最大的 region - Full GC:如果对象内存调配速度过快,混合 GC 还未回收实现,导致老年代被填满,就会触发一次 full gc,G1 的 full gc 算法就是单线程执行的 serial old gc,此过程与 CMS 相似,会导致异样长时间的暂停工夫,尽可能的防止 full gc.
-
- –
写到最初(点关注,不迷路)
文中或者会存在或多或少的有余、谬误之处,有倡议或者意见也十分欢送大家在评论交换。
最初,请敌人们 不要白嫖我哟 ,心愿敌人们能够 点赞评论关注 三连,因为这些就是我分享的全副能源起源????
-
- –
我曾经从零开始手写了简易版 springmvc,以及编写了具体的阐明文档,心愿可能帮忙搭档们深刻了解 springmvc 外围原理,有须要的敌人欢送关注公众号:
贝塔学 JAVA
, 回复源码
即可