首先我们要先明白什么是垃圾?就是没有任何指针指向的对象,可以成为可回收垃圾;
其次为什么需要垃圾回收?因为内存持续增长就像是生活垃圾持续产生得不到消耗,这样会造成程序问题,此时垃圾回收机制就要对程序内存进行不定时的管理和回收,以保证程序持续运行;
垃圾回收有两个步骤:
1. 垃圾确认算法:
就是标记阶段,以方便接下来的清除;
标记具体有两种算法:引用计数算法、GCRoot可达性分析算法;
🧃引用计数算法:
对于每个对象都给他一个变量,当在使用一次这个对象就随之将这个变量+1,反之-1;当这个变量为0的时候表示这个对象不再被引用,可进行回收;
优点:计算简单方便,判断效率高;
缺点:需要单独的字段去计算,消耗内存空间,每次加减操作,消耗时间,最大问题是对于循环引用它就无法处理;
🥦可达性分析算法:
涉及到一个概念“GCRoot”,所有的GCRoot都存放在一个set列表中进行管理,那么什么东西可以作为GCRoot呢?
总结一句话就是:一个指针保存了堆区的对象,但是他自身不在堆区;
例如:方法区静态常量、栈区的对象引用、本地方法栈的JNI引用...
优点:可以保证将每个内存垃圾进行回收;
缺点:需要遍历所有的root链,耗时;
2. 清除垃圾算法:
这一步骤才是正式清理垃圾;
清除垃圾有多种算法:标记清除算法、复制算法、压缩算法....;
所有的清除必须基于快照;快照指的是定于某个时间将所有的内存数据复制一份,这就是快照;因为不可能全量复制堆区,堆区就这么大,不可能全量复制,在进行GC的时候数据不能变动了,所以此时就要Stop the world STW,暂停用户程序。
🙂标记清除算法:
标记清除算法是最常见和使用的一种,他的标记使用的是可达性分析算法的方式,从引用根节点上开始标记所有被引用的对象;
清除:在堆内存从头到尾进行线性遍历,判断某对象的header中没有标记成可达性对象,则将其回收。
缺点:效率不高;容易产生大量的内存碎片;在进行GC时需要将用户的应用程序停掉,用户体验差。
🙃复制清除算法:
这就是S区域出现的原因,官方eden-S1-S2比例:8:1:1会开辟两块内存,将存活的对象复制到另外一块内存中,再将原本的全部清除,之后将指向地址转到新的内存中的对象中;
缺点:需要两倍空间;需要维护对象引用关系,时间开销大;适用于垃圾对象较少的情况,量级不大。
😌标记压缩/整理算法:
bu同于复制清除算法,它是在同一块内存中进行整理,先进行清除,后进行整理排序;
等同于标记清除算法后在进行一次内存碎片整理;
标记存活的对象被整理后,会按照内存地址依次排列,而未被标记的会被清除,如此一来我们需要给新的对象分配内存时,JVM只需要持有一个起始地址即可,减少了维护一个新的内存列表的开销。
这三个清除算法对比:
1. 复制算法最快,但是浪费内存;
2. 标记压缩、整理算法相对平滑,但是和标记清除算法相比多一个整理,和复制算法相比多一个标记阶段,效率不够好;
😃分代收集算法:
这三个没有一个较优的:所以出现分代收集算法;分为年轻代和老年代、S区域;
70%~99%是生命周期短,存活率低,产生应用频繁的对象;
1%~30%是生命周期长的对象,存活率高,回收不如年轻代频繁;
年轻代使用复制算法回收对象较快;
老年代使用的是标记整理算法:标记-清理-整理;所以GC在老年代的处理的时间是年轻代的10倍时间;
😃增量收集算法:
在垃圾回收的过程中,应用程序处于STW状态,暂停了应用程序所有的线程,导致回收整个过程都很长,影响用户体验;
增量收集是保证回收内存一部分一部分进行回收,在此期间不间断的切换垃圾回收线程和应用程序线程执行,直到垃圾回收完成;
允许垃圾收集以分阶段的形式进行,标记,清理,整理;
增量最具代表性的是——CMS:JDK1.5之后第一个真正意义上的并发处理垃圾回收器;低延时->用户线程停顿短暂;
第一步:标记GCRoot对象(STW时间短);
第二步:标记GCRoot整个引用链,消耗大,这一步开启并发,用户线程不停止,但是数据会有变动;
第三步:重新标记有变动的数据,会出现STW;
第四步:并发清理,不停用户线程;
第五步:整理是不定时的;
CMS标记流程三色标记法——降低延迟:
第一步初始标记:拿GC标记成灰色,如果没有引用标记成白色;
第二步并发标记——可达性分析:遍历完标黑色;
第三步重新标记——三色标记法:除了黑灰色状态,无状态对象再次标记;
第四步并发清理:清除白色;
😃分区算法:
堆内存越大,GC耗时越长,分区为了一次GC的时间缩短,更好的控制回收的停顿时间 ,每次合理的回收若干小区间。
内存回收通常使用的是复合算法,不是某一种算法。
网友评论