三、垃圾机制和垃圾收集器

159 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

三、垃圾机制和垃圾收集器

  • 大多数的对象都不是永生的,待其不再被使用时,面临它的就是被GC回收,因此就需要jvm判断哪些对象已经不再被使用,从而回收它;

👇先来介绍

  • 引用计数法:

    ​ 在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的。

    ​ 这种方法简单高效,但是它未能解决对象之间互相引用的问题,两个或两个以上对像明明在系统中已经不再被使用,但是由于他们之间互相引用,因此引用计数器永远不可能为o,也就意味着这批对象并不会被回收;所以一般也并不使用这种方式作为依据;

  • 可达性分析算法:

    ​ 简单来说就是由一个“GC Roots“ 作为根引用向下搜索引用对象,其中所经过的对象的路径称之为引用连,那么任何没有在引用连上的对象在垃圾回收时,都会被认为是垃圾对象回收掉,即使有对象之间互相引用的情况,也不会影响GC的回收,毕竟他们都不再引用链上;

  • 垃圾收集器

  • Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)

    ​ 这是一个单线程串行化的垃圾收器,在其进行垃圾回收时期会产生Stop The World,暂停用户线程直到垃圾回收结束,它在年轻代使用复制算法清理,老年代使用标记-整理算法清理;

    ​ 由于它的单线程的工作的特性,也就避免了线程间切换带来的开销,因此相对来说也可以获得很高的回收效率;

  • Serial Old收集器

    ​ Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,可以作为CMS 收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用,底层使用标记整理算法;

  • Parallel Scavenge收集器-XX:+UseParallelGC,-XX:+UseParallelOldGC

​	它更加关注于系统的吞吐量(系统运行代码的时间/拉近清理的时间+系统运行代码的时间),以吞吐量为优先的一款垃圾收集器;适合于高效利用CPU资源尽快完成运算;
  • ParNew收集器

    ​ ParNew收集器实质上是Serial收集器的多线程并行版本,年轻代的垃圾收集器,可以和CMS收集器配合使用;

  • CMS收集器(-XX:+UseConcMarkSweepGC(old))

    ​ cms垃圾收集器是一款多线程并发执行的收集器,是一种以获取最短回收停顿时间为目标的收集器,它可以让用户工作线程和垃圾回收线程同时工作在虚拟机内;

    ​ CMS是基于 标记清楚算法实现的,主要有下面几个步骤:

    1、初始标记

    ​ 以GC root为标记根节点,快速标记到直接引用的对象,这一过程会STW,但是时间会非常短;

    2、并发标记

    ​ 从以GC root直接关联的对象开始遍历整个对象内存,其中时间会比较长,但是不会STW,用户线程照常执行,因此在这段期间可能会存在标记过的对象状态发生改变的情况;

    3、重新标记

    ​ 这一阶段也会STW,用于修正在上一阶段因用户线程工作而产生的标记状态改变的情况;

    4、并发清理

    ​ 开启用户线程,同时GC线程开始对未标记的区域做清扫

    5、重置

    ​ 重置本次GC过程中的标记数据

    ​ 其中在初始标记和重新标记阶段会产生STW,但是时间会非常短,其他阶段都是并发处理;由此可以称得上低延时,并发处理的特点;

    ​ 由于标记清理算法在执行后会对内存造成很多的内存碎片,因此CMS可以通过设置XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做一次内存整理;

    ​ 由于用户线程和垃圾收集线程同时工作,难免的在这期间可能会产生没办法在此次垃圾回收期间难以标记清理的垃圾,称之为浮动垃圾,只能等下次GC再进行回收;

    还有G1垃圾收集器相对来说较为复杂,暂时不做赘述,后面再做总结;

👇【三色标记介绍及可达性分析扫描可能的问题】

​	在CMS垃圾收集器中,由于并发标记,并发清理的存在,在整个运行期间,对象之间的引用可能发生变化,因此对象发生多标、漏标的情况可能发生。

​	在**深入理解java虚拟机**一书中引入三色标记的方式来描述这种场景的发生;

首先:把Gcroots遍历对象过程中遇到的对象, 按照“是否访问过来标记成三种颜色;

* 黑色: 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描 过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过 灰色对象) 指向某个白色对象。 

* 灰色: 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。

*  白色: 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若 在分析结束的阶段, 仍然是白色的对象, 即代表不可达。

在可达性分析扫描的过程中在并标记阶段,用户线程仍然在工作,在这一阶段,可能上一个时间段被标记了仍然被引用着的对象,下一时间段方法执行结束GCroot销毁,与之关联的对象不会再被使用,但是此时已经被标记为安全的对象,将不会在这轮GC期间被清除,由于这种**多标**情况产生的不能被及时清理的对象称作**浮动垃圾**,在下一轮GC会被清理掉;

与之相对的还有一种**漏标**情况,就是原本应该是被标记成黑色的对象,由于在标记期间,可能程序还未运行到这个对象,导致被标记成了白色,所以在会被当做死亡对象清理掉,这种情况是不被允许的,属于一种严重的系统BUG;

因此,一般两种方式来解决并发扫描时由于漏标导致的对象消失问题

* 增量更新(CMS使用)

  ​	当黑色对象与白色对象建立新的引用关系时,会把这个新建的引用关系记录下来,等待并发扫描结束后,在以这些引用关系的黑色对象最为根节点再次扫描一次,重新标记;黑色对象与白色对象建立了引用关系,则黑色变成灰色对象;

* 原始快照(G1使用)

  ​	这种方式针对的是删除操作,当灰色对象要删除指向白色对象的引用关系时,就将这个要删 除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这样可以扫描到白色对象(安全存活);

🆕以上内容参考总结于 深入理解java虚拟机