深入浅出JVM(五)之垃圾收集器
1.垃圾收集器概述
1.如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
2.目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集
3.例如:Serial收集新生代采用复制算法,Serial Old收集年老代采用 标记-整理算法
4.ParNew新生代采用复制算法,老年代采用标记-整理算法, cms收集年老代采用 标记-整理算法
2.Serial收集器
1.串行垃圾回收器(Serial):它为单线程环境设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境
2.Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了
3.它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工
![](https://img2022.cnblogs.com/blog/2000608/202203/2000608-20220324073748649-2027257575.png)
3.Serial Old收集器
1.Serial Old收集年老代采用 标记-整理算法;
4.Parallel Scavenge收集器(并发)
1.Parallel收集器其实就是Serial收集器的多线程版本,新生代采用复制算法,老年代采用标记-整理算法
2.Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
3.Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU),CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)
4.所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。
5.JDK8默认的新生代和老年代收集器就是Parallel Scavenge收集器和Parallel Old收集器
6.参数设置:-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))
5.ParNew收集器
1.arNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
![](https://img2022.cnblogs.com/blog/2000608/202203/2000608-20220324111309779-2059162289.png)
6.CMS 收集器
1.CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器, 收集年老代采用 标记-整理算法
2.它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作
3.也就是说,CMS就是一个老年代的收集器
4.cms的低层算法使用了三色标记算法
6.1cms收集器的过程
1.cms收集器大致收集过程分为四个步骤: 初始标记(CMS initial mark),并发标记(CMS concurrent mark),重新标记(CMS remark),并发清除(CMS concurrent sweep)
2.cms收集器优缺点
1.从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿
2.它有下面几个明显的缺点:
1)对CPU资源敏感(会和服务抢资源),无法处理浮动垃圾
2)它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生
3)执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,
6.1.0CMS收集器收集完整步骤
1.第一阶段:Initial Mark【初始标记】2.第二阶段: Concurrent Mark 【并发标记】3.第三阶段: Concurrent Preclean【并发预先清除】
4.第四阶段: Concurrent Abortable Preclean【并发可能失败的预先清除】5.第五阶段: Final Remark【最终重新标记】
6.第六阶段 : Concurrent Sweep【并发清除】 7.第七阶段: Concurrent Reset【并发重置】
6.1.1初始标记阶段(CMS initial mark)
1.暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快
2.标记所有的根对象,包括根对象直接引用的对象,以及被年轻代中所有存活的对象所引用的老年代对象
6.1.2.并发标记阶段(CMS concurrent mark)
1.并发标记就是从GC Roots的直接关联对象开始遍历整个对象图的过程,标记存活的对象, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。
2. 因为在并发标记过程中,用户程序继续运行,可能会有导致已经标记过的对象状态发生改变, 如:年轻代的对象从年轻代晋升到老年代,
有些对象被直接分配到老年代,老年代和年轻代的对象引用关系变化
3.有可能存在漏标或者错误标记,因为标记线程和用户线程一起执行的,就会出现,已经标记过的对象又被其他对象所引用或者没有标记过的对象已经没有引用了
4.JVM会通过Card(卡片)
的方式将发生改变的老年代区域标记为“脏”区,这就是所谓的卡片标记(Card Marking)
6.1.3 并发预先清除阶段 (Concurrent Preclean)
1.也是用于标记老年代存活的对象,此阶段仍然是与应用线程并发执行的,不需要停止应用线程
2.目的: 让最终/重新标记的STW时间尽可能短
3.标记目标:老年代中在并发标记中被标记为“dirty”的card,幸存区(from和to)中引用的老年代对象
4.关闭参数:-XX:-CMSPrecleaningEnabled
,默认开启
6.1.4重新标记阶段(CMS remark)
1.重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录
2.这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法做重新标记
3.扫描范围: 新生代对象+GC Roots+被标记为“脏”区的对象。如果预清理阶段没有做好,这一步扫描新生代的时候就会花很多时间
6.1.5并发清除阶段(CMS concurrent sweep)
1.开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理,也就是三色标记算法.
2.此阶段与应用程序并发执行,不需要STW停顿。JVM在此阶段删除不再使用的对象,并回收他们占用的内存空间
6.1.6并发重置
1.此阶段与应用程序并发执行,重置CMS算法相关的内部数据,为下一次GC循环做准备
2.重置本次GC过程中的标记数据
6.2浮动垃圾
1.并发清理阶段用户线程还在运行,这段时间就可能产生新的垃圾,新的垃圾在此次GC无法清除,只能等到下次清理。这些垃圾有个专业名词:浮动垃圾
2.在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过(被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“浮动垃圾”
3.浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除,
6.3CMS的相关核心参数
1. -XX:+UseConcMarkSweepGC://启用cms
2. -XX:ConcGCThreads://并发的GC线程数
3. -XX:+UseCMSCompactAtFullCollection://FullGC之后做压缩整理(减少碎片)
4. -XX:CMSFullGCsBeforeCompaction://多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
5. -XX:CMSInitiatingOccupancyFraction:// 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
6. -XX:+UseCMSInitiatingOccupancyOnly://只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设 定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
7. -XX:+CMSScavengeBeforeRemark://在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
8. -XX:+CMSParallellnitialMarkEnabled://表示在初始标记的时候多线程执行,缩短STW
9. -XX:+CMSParallelRemarkEnabled://在重新标记的时候多线程执行,缩短STW;