GC(垃圾回收)知识点总结
GC包含四部分知识点,分别关于回收对象的判断,回收垃圾的算法,垃圾的分代回收,以及垃圾回收器
一、回收对象
1. 引用计数法
python、go中使用
判断被引用的次数,次数归0时回收,但如果遇到A调用B,B调用A的情况时则失效
2. 可达性分析算法
java虚拟机中使用的算法
根对象(GC Root):不能被回收的对象
扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收
3. 四种引用
强引用:根对象直接引用的对象
软引用&弱引用
相同点:没有被根对象直接引用的对象;都可以和引用队列联合使用,当软/弱引用所引用的对象被垃圾回收时,JVM就会把这个弱引用加入到与之关联的引用队列中
不同点:垃圾回收时,如果没有强引用直接引用的话,弱引用会直接被垃圾回收;软引用在内存不足时会被回收
虚引用
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
主要配合ByteBuffer使用
虚引用主要用来跟踪对象被垃圾回收的活动,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
软引用的常用场景
- 比如说数组申请的内存过大,那么可以使用软引用,这样内存过大时可以自动垃圾回收,等到需要的时候再开辟内存。
list->SoftReference->byte[] - 与引用队列配合使用
ReferenceQueue<byte[]> queue
new SoftReference<>(new byte[4MB], queue)
关联了引用队列,当软引用所关联的byte[]被回收时,软引用自己会加入到queue中去
可以从队列中获取那些被回收的软引用
弱引用使用WeakReference,用法和软引用基本一致
二、垃圾回收算法
1. 标记清除
第一阶段标记哪些没有被GC Root直接引用
第二阶段将这些标记的空间释放(释放的意思是将这些空间的起始和结束地址记录下来方便后面使用)
优点是清除速度比较快,缺点是容易产生内存碎片,因为不会对空间进行重新整理,空闲空间是不连续的
2. 标记整理
第一阶段与标记清除算法相同
第二阶段会把可用的对象向前移动,从而让内存更为紧凑
优点是不会产生内存碎片,缺点是速度比较慢
3. 复制
将内存区域分为FROM和TO,TO是空闲的
第一阶段与前面相同
第二阶段是把FROM区的可用对象复制到TO,然后交换FROM和TO的位置
优点是不会产生碎片且速度较快,缺点是需要双倍的内存空间
三、分代垃圾回收
1. 分代原因
两个分代假说:
弱分代假说:绝大多数对象都是朝生夕灭的
强分代假说:熬过越多次垃圾收集过程的对象就越难以小王
这两个假说决定了垃圾收集器划分不同区域的设计原则,新生代的大多数对象都是朝生夕灭的,那么把它们集中在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量空间;老生代对象都是难以消亡的对象,把他们集中在一起,虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存空间的有效利用
2. 跨代引用的收集
跨代引用假说:跨代引用相对于同代引用来说仅占极少数
基于这条假说,不需要为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需要在新生代上建立一个全局的数据结构,“记忆集”,这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用,之后扫描时,只要这些对应内存会被加入GC Roots扫描。
至于如何维护这个记忆集,主要是运用写屏障技术,创建一个当新生代某个对象发生更新后的切面,进行一些更新后的修改记忆集的操作
3. 并发的可达性分析
三色标记:
白色:表示对象尚未被垃圾收集器访问过
黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过
灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过
可能的问题:
把原本消亡的对象错误标记为存活;把原本存活的对象错误标记为已消亡
解决方案:
第一种问题是可以容忍的,下次垃圾收集清理掉即可;而第二种问题是不可以容忍的
第二种问题发生的充要条件:
赋值器插入了一条或多条从黑色对象到白色对象的新引用
复制器删除了全部从灰色对象到该白色对象的直接或间接引用
因此,我们要解决并发扫描时的对象消失问题,只需破坏这两个条件的任意一个即可,也就由此产生了两种解决方案:增量更新和原始快照
增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为:黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了,CMS使用的是这种解决方案
原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为:无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索,G1使用的是这种解决方案
四、垃圾回收器
1. 串行垃圾回收器
进行垃圾回收时全程STW
新生代使用复制算法,老年代使用标记整理算法
2. 并行垃圾回收器
除了使用多个GC线程进行垃圾收集外,其他与串行一样
3. CMS垃圾回收器
CMS(Concurrent Mark Sweep)收集器是一种以获得最短回收停顿时间为目标的收集器,基于标记-清楚算法,分为4个阶段:
初始标记:标记一下GC Roots能直接关联到的对象,会STW
并发标记:GC Roots Tracing,可以和用户线程并发执行
重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会STW
并发清除:清除对象,可以和用户线程并发执行
CMS的缺点:
CMS默认启动的回收线程数是(CPU数量+3)/4,CPU少的时候占的CPU较多,会影响用户线程的执行
基于标记-清除算法,无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生
concurrent mode failure
该问题是在执行CMS GC的过程中同时业务线程将对象放入老年代,而此时老年代空间不足,或者在做Minor GC的时候,新生代Survivor空间放不下,需要放入老年代,而老年代也放不下而产生的。
解决方案:在进行一定次数的标记清楚后进行一次标记整理算法
4. G1垃圾回收器
G1分区:
分区中将Eden区,Survivor区,Old区,Humongous(巨型对象区域)区放在一个个region中
G1整体上基于标记-清除算法,但是局部上基于复制算法,也就意味着不会产生空间碎片,一共分为4个阶段:
初始标记:标记GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Set)的值,让下一个阶段用户程序并发运行时,能在正确可用的Region中创建新对象,STW
并发标记:从GC Roots开始对堆中对象进行可达性分析,找到存活的对象
最终标记:修正并发标记期间因用户程序继续运行而导致标记产生变化的那一部分标记记录,STW
筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来确定回收计划
G1 GC的分类:
young gc
mix gc
full gc:G1不提供full gc,使用串行GC进行full gc
触发场景:
1、从年轻代分区拷贝存活对象时,无法找到可用的空闲分区
2、从老年代分区转移存活对象时,无法找到可用的空闲分区
3、分配巨型对象时在老年代无法找到足够的连续分区
G1的缺点:Full GC使用的是串行GC效率很差;不适用与小型内存的回收,因为G1优于对局部进行回收
5. ZGC垃圾回收器
待更新