常见垃圾回收器

  1. Serial New收集器是针对新⽣代的收集器,采⽤的是复制算法;
    2. Parallel New(并⾏)收集器,新⽣代采⽤复制算法,⽼年代采⽤标记整理;
    3. Parallel Scavenge(并⾏)收集器,针对新⽣代,采⽤复制收集算法;
    4. Serial Old(串⾏)收集器,新⽣代采⽤复制,⽼年代采⽤标记清理;
    5. Parallel Old(并⾏)收集器,针对⽼年代,标记整理;
    6. CMS收集器,基于标记清理;
    7. G1收集器:整体上是基于标记清理,局部采⽤复制;
    GC的分类
    1.按照线程数来进行划分,可以分为串行垃圾回收器和并行垃圾回收器
    串行垃圾回收器:同一时间段只允许一个cpu用于执行垃圾回收操作,此时垃圾线程被暂停,直至垃圾收集工作结束
    并行垃圾回收器:并行收集可以运用多个cpu同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收和串行回收一样,采用独占式,使用了STW机制
    image.png
    2.按照工作模式进行划分,可以分为并发式垃圾回收器和独占式垃圾回收器;
    并发式垃圾回收器与应用程序交替工作,以尽可能减少应用程序的停顿时间;
    独占式垃圾回收器(STW)一旦运行,就需要停止应用程序中的所有用户线程,直到垃圾回收过程完全结束为止;
    image.png
    3.按照碎片处理方式划分:可以分为压缩式垃圾回收器和非压缩式垃圾回收器
    压缩式垃圾回收器会在回收完成以后,对存活对象进行压缩整理,消除回收后的碎片;
    非压缩式的垃圾回收器不进行碎片的压缩整理工作
    4.按照工作的内存区间划分:可以分为年轻代垃圾回收器和老年代垃圾回收器

不同的垃圾回收器概述
image.png

image.png

同时,垃圾收集器在年轻代和老年代的组合使用关系随着jdk版本的演进发生着一些变化:
1.两个收集器之间有连线,表明他们可以配合使用:
Serial GC/Serial Old GC
Serial GC/CMS
ParNew GC/Serial Old GC
ParNew GC/CMS
Parallel Scavenge GC/Serial Old GC
Parallel Scavenge GC/Parallel Old GC
G1 GC
2.其中,Serial Old GC 作为CMS 出现”Concurrent Mode Failure”失败的后备方案.
3.(红色虚线)由于维护和兼容性测试的成本,在JDK 8时将Serial Old + CMS,ParNew + Serial Old这两个组合声明为废弃,同时,在JDK9中完全取消了这两个组合的支持(即移除)
4.(绿色虚线)JDK14中,弃用了Parallel Scavenge和Serial Old的组合
5.(青色虚线)JDK14中,删除了CMS垃圾回收器
image.png

如何查看默认的垃圾收集器?
-XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID

Serial回收器———串行回收
1.Serial收集器是最基本,历史最悠久的垃圾收集器了.JDK1.3之前回收新生代唯一的选择;
2.Serial GC作为HotSpot中Client模式下的默认新生代垃圾回收器
3.Serial GC 采用的是复制算法,串行回收和”Stop The World”机制的方式执行垃圾内存回收的
4.除了年轻代之外Serial 收集器还提供了用于执行老年代垃圾收集的Serial Old GC,Serial Old GC收集器同样也采用了串行回收和”Stop The World”机制,只不过内存回收算法使用的是标记-压缩算法.
注意:
1.Serial Old是运行在Client模式下的默认的老年代的垃圾收集器
2.Serial Old在Server模式下主要有两个用途:一是与新生代的ParallelScavenge配合使用,二是作为老年代CMS收集器的后备垃圾收集方案
image.png
Serial垃圾收集器是一个单线程收集器,但是它的”单线程”的意义并不仅仅说明他只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是在他进行垃圾收集的时候,必须暂停其他所有的工作线程,直到他收集结束(Stop The World).
image.png
ParNew回收器———并行回收
ParNew GC:即是Serial GC的多线程版本
ParNew收集器除了采用并行回收的方式进行内存垃圾回收以外,与Serial几乎没有其他区别:同样采用复制算法,”Stop The World”机制;
ParNew是很多JVM运行在Server模式下年轻代的默认垃圾收集器
ParNew GC的工作流程如下所示:
对于新生代来说,回收次数频繁,则使用并行方式高效
对于老年代,回收次数少,则使用串行方式节省资源
image.png

image.png
Parallel回收器———吞吐量优先
HotSpot的年轻代中除了拥有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同样采用了复制算法,并行回收,“Stop The World”机制”;
那么是否Parallel收集器的出现是多此一举呢?
1.和ParNew收集器不同,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量,它也被称作吞吐量优先的垃圾收集器;
2.自适应调节策略也是一个重要的区别;

高吞吐量则是可以高效地利用cpu时间,尽快的完成程序的运算任务,主要适合后台运算而不需要太多交互的任务.因此,常见在服务器环境中使用.例如,那么执行批量处理,订单处理,工资支付,科学计算的应用程序;
Parallel 收集器在JDK1.6时提供了执行老年代收集的Parallel Old 收集器,用来替代老年代的Serial Old 收集器
Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收和”Stop The World”机制;
Paralel Scavenge GC/Parallel Old GC工作流程入下图所示:
image.png
注意:
1.在程序吞吐量优先的应用场景中,Parallel收集器和Parallel Old收集器的组合,在Server模式下的内存回收性能很不错;
2.在Java8(目前开发热流版本)中,默认的垃圾收集器即是Parallel GC;
Parallel GC的相关参数配置:
image.png

image.png

image.png
CMS回收器———低延迟
image.png
CMS作为老年代的收集器,却无法与新生代收集器Parallel Scavenge 配合工作,所以在JDK1.5中使用CMS来收集老年代时,年轻代只能选择ParNew或者Serial收集器中的一个.
在 G1出现之前,CMS使用非常广泛;
CMS GC执行垃圾回收的过程:
image.png
CMS的垃圾收集过程可以分为四个主要的阶段:

初始标记阶段:程序的所有的工作线程会因为“Stop-The-World”机制而出现短暂的暂停(此时停顿第一次),这个阶段的主要任务仅仅只是标记出GC Roots能关联到的对象。一旦标记完成,就会恢复之前被暂停的所有的应用线程。由于直接关联的对象比较地小(因为此时在老年代中进行垃圾回收,能被GC Roots标记到的对象很少),所以这里的速度会很快!!

并发标记阶段:从GC Roots的直接关联对象开始遍历整个对象图,这个过程耗时比较长,但是不需要停顿用户线程,可以与垃圾收集收集线程一起并发运行

重新标记阶段:由于在并发标记阶段中,程序的工作线程一一直在运行过程中 ,可能会由于和垃圾的收集线程交叉运行导致之前的一些标记会发生一些变动,故进行标记的修正,这个过程比初始标记略长(STW,此时停顿第二次),但比并发标记的时间要短很多

并发清除阶段:清理删除掉标记阶段已被判断为已经死亡的对象,并释放内存空间,由于不需要移动内存对象的位置,则此阶段也是可以和用户线程同时并发执行的
image.png

image.png

image.png
CMS GC的特点:
优点:
1.并发收集垃圾
2.低延迟性
缺点:
1.因为采用的是标记-清除算法,所以会产生内存的碎片;
2.CMS收集器对cpu资源非常地敏感;在并发阶段,它虽然不会导致用户的停顿,但是会占用一部分的线程而导致应用程序变慢,总的吞吐量会降低
3.CMS无法清除浮动垃圾;可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生,在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发阶段如何产生了一些新的垃圾对象,此时CMS将无法对这些垃圾进行标记,这些垃圾对象也被称为浮动垃圾,最终导致这些新产生的垃圾对象没有被及时的回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。
CMS收集器参数设置:
image.png

image.png

image.png
G1回收器———区域化分代式
G1收集器(Garbage First): JDK7中引入的垃圾回收器,是目前收集器技术发展的最前沿成果之一;
取名为G1收集器的原因:G1收集器将堆内存分割成很多不相关的区域(region),其物理上是不连续的,使用不同的region来表示Eden区,S0区,S1区老年代等等;G1收集器会跟踪region里面垃圾堆积的价值大小(根据回收所获得的空间大小以及回收所需的时间的经验值等),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。故由于这种方法侧重于回收垃圾的最大量的区间,所以我们取名为G1 GC。
优点:
1.并发和并行性:并行性表现在G1在回收期间,可以由多个GC线程同时工作,有效的利用多核计算能力,此时用户线程STW;并发性表现在G1拥有和应用程序交替执行的能力,部分工作能够和应用程序同时执行。
2.分代收集:它会区分老年代和新生代,年轻代依旧还是Eden区和Survivor区,但是它并不要整个Eden区,年轻代或者老年代都是连续的,也不坚持固定大小和固定的数量。将堆空间分为了若干个区域,这些区域中包含了逻辑上的年轻代和老年代。但是有一点,它和其他的垃圾收集器都不相同,即它同时可以兼顾年轻代和老年代的垃圾回收。
3.空间的整合:G1将内存划分为一个个的region,内存的回收都是以region为单位进行的,region之间使用的是复制算法,但是整体上使用的是标记-压缩算法,两种算法都是可以避免内存碎片的产生。这种特性有利于程序的长时间的运行,分为大对象时不会因为无法找到连续内存空间而提前触发下一次Full GC。尤其是当java的堆内存非常大的时候,G1的优势会非常地明显。
4.可预测的停顿时间模型:即软实时,G1除了追求低停顿以外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段中,消耗在垃圾回收的时间不超过N毫秒。
缺点:
1.垃圾收集时产生的内存占用相较于CMS而言,是要高于它的;
2.程序在运行时额外执行负载(OverLoad)也要高于CMS产生的OverLoad

G1 GC的分区(Region)思想:
image.png

image.png
多出了一个H区(Humongous区):
对于堆中的大对象,默认直接会被分配到老年代中,但是如果让是一个短期存在的大对象,就会对垃圾收集器造成负面影响.为了解决这个问题,G1划分了一个Humongous区,它专门用来存放大对象,如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储.为了能够找到连续的H区,有的时候不得不启动Full GC,G1的大多数行为都是把H区作为老年代的一部分来看待的.
G1收集的三个环节:
image.png
1.年轻代GC(YoungGC/MinorGC): jvm启动的时候,G1会先准备好Eden区,程序在运行过程中,会不断地产生对象到Eden区中,当Eden区的空间耗尽的时候 ,G1会启动一次年轻代垃圾回收过程(YoungGC/MinorGC):具体回收过程如下所示:

1、 G1会停止应用程序的执行(Stop The World),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区的所有的内存分段
2、扫描根:根指的就是GC Roots,根和Rset记录的外部引用作为扫描存活对象的入口
3、更新Rset:处理dirty card queue中的card,更新Rset,此阶段结束时,Rset可以准确的反应老年代对所在的内存分段中对象的引用
4、处理Rset:识别被老年代指向的Eden区的对象,这些被指向的Eden中的对象被认为是存活的对象
5、复制对象:遍历对象树,Eden区中内存段中存活的对象会被复制到Survivor区的空白的内存分段(底层即是复制算法),Survivor区中存活的对象如果年龄未达阈值,则年龄加1,达到阈值则被复制到Old区的空的内存分段,如果Survivor区中内存空间不足。则可以将Eden区中的部分数据直接晋升老年代空间
6、处理引用:处理soft,Weak,Phantom,final等引用,最终使得Eden区变空。GC停止工作,因为目标内存中在复制过程中是连续存储的,没有碎片产生,所以复制过程中,可以达到内存整理的效果
注意:上面提到Rset:Remembered Set解释一下其来源和含义
Remembered Set: 因为我们说堆空间被分割成了若干个region,但是每一个region并不是相互独立的,很多时候,一个region里的对象有可能被其他的region里的对象所引用,则我们在扫描过程中寻找存活的对象和已经死亡的对象时,是否需要扫描整个java堆呢??为了避免全局扫描整个堆区耗费大量的时间,此时引进Rset:即给每个Region分配对应的一个Rset,每次Reference类型数据写操作时,都会产生一个Write Barrier暂时的中断操作,然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region,如果不同,则将相关的引用信息记录到引用指向的对象的Region对应的Rset中,当进行垃圾收集的时候,在GC根节点的枚举范围加入Rset,就可以保证不进行局部扫描,也不会存在遗漏。

2.并发标记过程:
1、 初始化标记扫描:标记从根节点可以直接可达的对象(STW)
2、根区域扫描:G1GC扫描Survivor区可以直接可达的老年代的区域对象,并标记被引用的对象,这一过程必须在YoungGC之前完成
3、并发标记:在整个堆中进行并发标记(和应用程序并发执行),若发现区域对象中的所有对象都是垃圾,则这个区域会被立即回收,同时,标记过程中,会计算每个区域中的对象活性(即区域中存活对象的比例)
4、再次标记:由于应用程序持续进行,需要进一步修正上一次的标记结果(STW),此阶段中G1采用比CMS更快的初始快照算法
5、独占清理:计算各个区域中存活的对象的存活情况和GC回收比例,并进行排序,识别可以混合回收的区域(STW)
6、并发清理阶段:识别并清理完全空闲的区域
3.混合回收
当越来越多的对象晋升成老年代时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法除了回收真个Young区,还会回收一部分的Old区。其中,混合回收的回收集包括1/8的老年代内存片段,Eden区片段,Survivor区内存片段。混合回收的算法和年轻代回收的算法完全一致,只是回收集多了老年代的内存片段。
4.Full GC
G1的初衷就是避免Full GC的出现,但是如果上述方式不能正常工作,则G1会停止应用程序的执行(Stop The World),使用单线程的内存回收算法进行垃圾回收,性能会比较差,应用停顿时间比较长;
G1垃圾会收取优化建议:
image.png
G1 GC的相关参数设置:
image.png
垃圾回收器总结和发展
image.png

image.png

image.png