赞
踩
继上篇文章《JVM垃圾回算法详解》,我们本篇文章来讲解一下JVM垃圾回收器。垃圾标记算法是标记哪些对象是垃圾,垃圾回收算法是回收的理论算法,垃圾回收器才是真正回收垃圾的组件。不同的垃圾回收器使用了不同的垃圾回收算法
在 JVM 中,具体实现有 Serial、ParNew、Parallel Scavenge、CMS、Serial Old(MSC)、Parallel Old、G1 等。在下图中,你可以看到 不同垃圾回收器 适合于 不同的内存区域,如果两个垃圾回收器之间 存在连线,那么表示两者可以 配合使用,如下图:
Serial是一款用于新生代的单线程收集器,采用复制算法进行垃圾收集
。Serial进行垃圾收集时,不仅只用一条线程执行垃圾收集工作,它在收集的同时,所有的用户线程必须暂停
(Stop The World)。它们的设计初衷是为了适应早期的硬件环境和应用场景。在那个时候,硬件配置相对较低,主要特点包括内存容量较小、CPU 单核、并发应用场景相对较少。基于这些限制条件,Serial 系列的垃圾收集器采用了简单高效、资源消耗最少、单线程收集的设计思路。
但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率
如下是Serial收集器和Serial Old收集器结合进行垃圾收集的示意图,当用户线程都执行到安全点时,所有线程暂停执行,Serial收集器以单线程,采用复制算法
进行垃圾收集工作,收集完之后,用户线程继续开始执行。
Serial Old
收集器是Serial收集器的老年代版本,它同样是一个单线程收集器采用标记整理算法
。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
总结:Client模式(桌面应用), 适用于单核服务器,可以避免CPU切换,可以用-XX:+UseSerialGC来选择Serial作为新生代收集器。使用 XX:+UseSerialOldGC来选择SerialOld作为老年代收集器。Serial 新生代回收器 采用单线程+复制算法,SerialOld 老年代采用单线程+标记整理算法。
随着硬件资源的升级,包括内存空间的增大和 CPU 的多核化,传统的 Serial 垃圾收集器面临着性能瓶颈。由于它采用单线程执行垃圾回收操作,无法充分利用多核 CPU 的优势,导致在处理大内存空间时性能下降,垃圾回收时间变得更长。为了充分发挥多核 CPU 的优势,JVM 推出了 Parallel 收集器系列。Parallel 收集器的设计思想是利用多线程并行执行垃圾回收操作
,以提高整个垃圾收集过程的并行度和性能。
除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等
等)和Serial收集器类似。默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。
Parallel Scavenge 回收器关心的是程序运行的吞吐量
。吞吐量指的是一段时间内,用户代码 运行时间占 总运行时间 的百分比
。那么如果用户代码的允许时间越长,吞吐量越高。可以通过-XX:MaxGCPauseMillis来设置收集器尽可能在多长时间内完成内存回收,可以通过-XX:GCTimeRatio来精确控制吞吐量。
如下是Parallel收集器和Parallel Old收集器结合进行垃圾收集的示意图,在新生代,当用户线程都执行到安全点时,所有线程暂停执行,Parallel收集器以多线程,采用复制算法
进行垃圾收集工作,收集完之后,用户线程继续开始执行;在老年代,当用户线程都执行到安全点时,所有线程暂停执行,Parallel Old收集器以多线程,采用标记整理算法进行垃圾收集工作。
适用场景:注重吞吐量,高效利用CPU,需要高效运算且不需要太多交互。可以使用-XX:+UseParallelGC来选择Parallel Scavenge作为新生代收集器,jdk7、jdk8默认使用Parallel Scavenge作为新生代收集器。
ParNew 和 Parallel Scavenge 垃圾收集器在实现上确实有一些相似之处,都属于并行垃圾收集器。但 ParNew 垃圾收集器之所以出名,一个重要原因是它是唯一能与 CMS(Concurrent Mark-Sweep)收集器配合使用的新生代收集器,特别适用于那些对停顿时间要求较高的应用场景。
如下是ParNew收集器和Serial Old收集器结合进行垃圾收集的示意图,当用户线程都执行到安全点时,所有线程暂停执行,ParNew收集器以多线程,采用复制算法
进行垃圾收集工作,收集完之后,用户线程继续开始执行。
适用场景:多核服务器;多与CMS收集器搭配使用。当使用-XX:+UseConcMarkSweepGC来选择CMS作为老年代收集器时,新生代收集器默认就是ParNew,也可以用-XX:+UseParNewGC来指定使用ParNew作为新生代收集器。
总结:ParNew 新生代回收器 采用的是 复制算法,多线程回收,多核CPU下,性能高于Serial
CMS(Concurrent Mark Sweep) 回收器是在 最短回收停顿时间 为前提的回收器,属于 多线程回收器,采用 标记-清除算法。它非常符合在注重用户体验
的应用上使
用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
。
相比之前的收集器,CMS收集器的运作过程比较复杂,分为四步:
记录下gc roots直接能引用的对象
,速度很快。GC Roots的直接关联对象开始遍历整个对象图的过程
, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行
。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象
的标记记录(主要是处理漏标
问题),会暂停用户线程(STW),这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法
做重新标记。对未标记的区域做清扫
。这个阶段如果有新增对象会被标记为黑色不做任何处理总结:CMS的有点在于尽可能减少用户线程的停顿时间,整个过程耗时最长的并发标记和并发清除都是和用户线程一起工作,所以从总体上来说,CMS收集器垃圾收集可以看做是和用户线程并发执行的。
CMS收集器也存在一些缺点:
占用CPU资源越多,吞吐量越小
清除已标记的垃圾(处于最后一个阶段)时,用户线程还在运行,因此会有新的垃圾产生,但是这部分垃圾未被标记,在下一次GC才能清除,因此被成为浮动垃圾
。由于内存回收和用户线程是同时进行的,内存在被回收的同时,也在被分配。当老生代中的内存使用超过一定的比例时,系统将会进行垃圾回收;当剩余内存不能满足程序运行要求时,系统将会出现Concurrent Mode Failure,临时采用Serial Old算法进行清除
,此时的性能将会降低执行完标记清除后再做整理
在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况
就有可能发生。漏标的问题主要引入了三色标记算法来解决
。
三色标记算法是把Gc roots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色
如果某GC Root对象及其应用对象都已经被扫描过标记为黑色,但是由于方法结束栈帧销毁导致GC Root对象释放,但是其引用的对象又被标记为黑色不可被回收,那么这样的对象称之为:浮动垃圾,这部分垃圾不会被回收需要等到下一次重新被扫描清除。
在并发标记过程中,用户线程也在一起工作,当某对象©失去引用,GC无法扫描到它将会被识别为垃圾(白色),等并发标记结束该对象©重新被其他对象引用(A),按道理说C现在不应该是垃圾了,但是在并发标记中已经被识别为垃圾,那么如果GC把C清理掉就会出现就是不合理的情况,C就是漏标的情况。
漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB)
。
增量更新
就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来
, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次
。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。
原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来
, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次
,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象
在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障
实现的。
所谓的写屏障,其实就是指在赋值操作前后,加入一些处理,对于写屏障实现SATB来说,当对象B的成员变量的引用发生变化时,比如引用消失(b.c = null),我们可以利用写屏障,将B原来成员变量的引用对象C记录下以便后续再次扫描。
对于:写屏障实现增量更新当对象A的成员变量的引用发生变化时,比如新增引用(a.c = c),我们可以利用写屏障,将A新的成员变量引用对象c记录下来以便后续再次扫描。 CMS采用写屏障+增量更新,G1采用的是写屏障+SATB
在新生代做GCRoots可达性扫描过程中可能会碰到跨代引用的对象
,也就是老年代中的对象引用了新生代的对象,这种如果又去对老年代再去扫描效率太低。为此,在新生代可以引入记录集(Remember Set)的数据结构(记录从非收集区到收集区的指针集合)用来记录垮代引用的情况
,避免把整个老年代加入GCRoots扫描范围。
hotspot使用一种叫做“卡表”(Cardtable)的方式实现记忆集
,也是目前最常用的一种方式。关于卡表与记忆集的关系, 可以类比为Java语言中HashMap与Map的关系。
卡表是使用一个字节数组实现:CARD_TABLE[ ](在年轻代堆中),每个元素对应着其标识的内存区域一块特定大小(512KB)的内存块,称为“卡页”(把老年代分为大小相等等区域)
一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1,表示该元素变脏,否则为0 。GC时,只要筛选本收集区的卡表中变脏的元素加入GCRoots里。
CMS的相关核心参数
- -XX:+UseConcMarkSweepGC:启用cms
- -XX:ConcGCThreads:并发的GC线程数
- -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
- -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
- -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
- -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
- -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在
minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段- -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
- -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
这里我们简单对比一下几款垃圾回收器Serial就不说了,单线程工作,适用于单核机器,在如今基本上是不会被使用的。Parallel 和 CMS 而言,前者虽然是多线程工作但是在垃圾回收的过程中会触发STW机制这会导致用户线程停顿,如果对于JVM内存较大的情况下,那么垃圾回收的过程会比较长,用户停顿的时间也会比较长,这会可能会导致整体性能不好。所以:Parallel更适合小内存的情况下使用,比如:4G一下的内存。
而CMS初始标记会STW,但是这个阶段只会标记GC Root引用的对象,速度非常快,最耗时的并发标记是和用户线程一起工作的,不会停顿用户线程,重新标记是处理漏标的对象也比较快,所以整体来说。CMS是用户线程最少停顿的,如果内存较大的情况下建议使用CMS。性能较高。
G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器
. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征
传统的垃圾收集器把堆内存划分成老年代和年轻代,每次回收的粒度都是整个分代,G1垃圾收集器把堆内存划分成了更小的Region
,在逻辑上还是有老年代和年轻代的概念,年轻代和老年代都拥有多个Region,堆内存中一个区域(Region)的大小可以通过-XX:G1HeapRegionSize参数指定,在每次垃圾回收时,G1会选择一些存活对象较少的Region进行回收,每个Region在逻辑上不是固定的年轻代和老年代,有可能现在是年轻代,下次垃圾回收后再分配时就可能成为老年代。
JVM最多可以有2048个Region。一般Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M,当然也可以用参数"-XX:G1HeapRegionSize"手动指定Region大小,但是推荐默认的计算方式。
默认年轻代对堆内存的占比是5%
,可以通过“-XX:G1NewSizePercent”设置新生代初始占比,在系统运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%
,可以通过“-XX:G1MaxNewSizePercent”调整。年轻代中的Eden和Survivor对应的region也跟之前一样,默认8:1:1。
一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代,也就是说Region的区域功能可能会动态变化。
G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样,唯一不同的是对大对象的处理,G1有专门分配大对象的Region叫Humongous区
,而不是让大对象直接进入老年代的Region中。在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,而且一个大对象如果太大,可能会横跨多个Region来存放。
Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老年代空间不够的GC开销。Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收。
G1的垃圾回收流程和CMS非常类似,分为四个步骤
记录下gc roots直接能引用的对象
,速度很快。GC Roots的直接关联对象开始遍历整个对象图的过程
, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行
。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象
的标记记录(主要是处理漏标
问题),会暂停用户线程(STW),这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的原始快照SATB算法
做最终标记。在用户规定的时间范围内优先回收价值最高的Region
,尽量把GC导致的停顿时间控制在我们指定的范围内。回收算法主要用的是复制算法,将一个region中的存活对象复制到另一个region中,然后把当前region清理掉
,这种不会像CMS那样回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多内存碎片。回收价值计算
G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region
(这也就是它的名字Garbage-First的由来),比如一个Region花200ms能回收10M垃圾,另外一个Region花50ms能回收20M垃圾,在回收时间有限情况下,G1当然会优先选择后面这个Region回收。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内可以尽可能高的收集效率
总结一下G1垃圾回收器的特点
综合来看:G1不仅可以控制GC的垃圾回收时间,也可以做到和用户线程并发执行,使得G1在不同应用场景中取得关注吞吐量和关注延迟之间的最佳平衡。
YoungGC
YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做YoungGC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC
MixedGC
不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent) 45% 设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做 MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC
Full GC
停止系统程序,然后采用单线程进行标记、清理和压缩整理
,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。(Shenandoah优化成多线程收集了)
SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象
的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描
-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的线程数量
-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
-XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
-XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
-XX:G1MaxNewSizePercent:新生代内存最大空间
-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
-XX:MaxTenuringThreshold:最大年龄阈值(默认15)
-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能就要触发MixedGC了
-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这个值,存活对象过多,回收的的意义不大。
-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。
-XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。
G1使用场景建议
假设参数 -XX:MaxGCPauseMills 设置的值很大,导致系统运行很久,年轻代可能都占用了堆内存的60%了,此时才触发年轻代gc。那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。或者是你年轻代gc过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor区域的50%,也会快速导致一些对象进入老年代中。所以这里核心还是在于调节 -XX:MaxGCPauseMills 这个参数的值,在保证他的年轻代gc别太频繁的同时,还得考虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc.
ZGC,全称为Z Garbage Collector,是Oracle公司开发的一款面向大型、多核、高内存应用的低延迟、高吞吐量的垃圾回收器,ZGC致力于将最大停顿时间控制在10毫秒以内,从而满足那些对响应时间有着极高要求的应用场景。此外,ZGC还支持从8MB到16TB的堆内存大小,为大内存应用提供了强有力的支持。
读屏障的堆栈式替换算法和基于标记颜色的压缩算法
,避免了传统GC中的根扫描和整理等阶段,从而大幅减少了GC暂停时间
。同时,ZGC还采用了并发标记和并发重分配等技术,使得大部分GC工作都可以与应用程序线程并发执行,进一步降低了应用程序的停顿时间。多线程并行处理垃圾回收任务
,以及使用更大的堆空间和更高效的内存分配器等技术,ZGC能够在保证低延迟的同时,实现高吞吐量的垃圾回收ZGC收集器是一款基于Region内存布局的, 暂时不设分代的, 使用了读屏障、 颜色指针等技术来实现可并发的标记-整理算法的, 以低延迟为首要目标的一款垃圾收集器。ZGC的Region可以具有如图3-19所示的大、 中、 小三类容量:
ZGC采用了基于标记-复制的算法,并对该算法进行了重大改进。在标记、转移和重定位阶段,ZGC几乎都是并发的,从而实现了极短的停顿时间。同时,ZGC还采用了染色指针和内存多重映射等技术,提高了内存管理的效率和灵活性。
具体来说,ZGC的工作过程可以大致分为以下几个阶段:
ZGC目前有4中机制触发GC:
ZGC最大的问题是浮动垃圾。ZGC的停顿时间是在10ms以下,但是ZGC的执行时间还是远远大于这个时间的。假如ZGC全过程需要执行10分钟,在这个期间由于对象分配速率很高,将创建大量的新对象,这些对象很难进入当次GC,所以只能在下次GC的时候进行回收,这些只能等到下次GC才能回收的对象就是浮动垃圾
在Java虚拟机(JVM)中,安全点和安全区域是两个与垃圾收集(GC)过程密切相关的概念。它们共同确保了垃圾收集过程的正确性和高效性。
总结来说,JVM通过安全点和安全区域这两个机制,确保了垃圾收集过程的正确性和高效性。安全点使得JVM能够在特定的代码位置暂停应用程序的执行,而安全区域则保证了在垃圾收集期间,执行线程对堆中对象的访问是安全的。这两个机制的协同工作,为Java程序的稳定运行提供了有力的保障
文章结束喜欢请各个好评哦!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。