垃圾收集算法
分代收集理论:根据对象的存活周期将java堆分为年轻代和老年代,根据不同年代选择不同的垃圾收集算法,一般年轻代使用复制算法,老年代使用标记整理,标记清除算法。
复制算法:内存一分为二,内存使用完之后将还存活的对象复制到另一边,再把使用过的空间一次性清理。
优点:效率高,没有内存碎片
缺点:内存大小变成原来的一半,对象存活率高时复制会很频繁
标记整理算法:标记存活对象,所有存活对象向一端移动,然后清理掉端边界以外的内存
优点:没有内存碎片的问题
缺点:对象需要移动,降低了效率
标记清除算法:标记存活的对象,同一回收未被标记对象
优点:不需要对象移动
缺点:会产生大量内存碎片,标记对象太多效率不高
垃圾收集器
Serial收集器:串行收集器,单线程,启动时会STW(stop the world)暂停用户线程,效率比较高。年轻代采用复制算法,老年代采用标记整理算法。年轻代使用Serial收集器,老年代使用Serial Old收集器。
参数设置(-XX:+UseSerialGC -XX:+UseSerialOldGC)
Parallel Scavenge收集器(jdk8默认):并行垃圾收集器,启动时会STW暂停用户线程,是Serial的多线程版本,高效利用cpu,提高吞吐量。吞吐量:cpu用于运行用户代码时间与cpu总耗时的比值。年轻代采用复制算法,老年代采用标记整理算法。年轻代使用Parallel Scavenge收集器,老年代使用Parallel Old Scavenge收集器。
参数设置:(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))
ParNew收集器:与Parallel 类似,多线程并发暂停用户线程,但是ParNew(用于年轻代)可以与CMS(用于老年代)搭配使用。年轻代采用复制算法,老年代采用标记整理算法。
参数设置:(-XX:+UseParNewGC)CMS收集器:CMS(Concurrent Mark Sweep)并法标记清除垃圾收集器。使用标记清除算法实现,用于老年代的垃圾收集。分为4步。
初始标记:会STW暂停用户线程,找GC Roots直接引用的对象,速度很快
并发标记:GC Roots关联对象遍历整个对象,无需暂停用户线程,速度慢
重新标记:修正并发标记因用户线程运行变动的那部分标记对象,会STW暂停用户线程,使用三色标记里的增量更新算法,重新标记。
并发清理:清理未标记对象,无需暂停用户线程。
优点:并发收集,低停顿。
缺点:①会和cpu抢资源,②无法处理浮动垃圾(在并发标记和并发清理期间会产生新的垃圾,这种浮动垃圾只能下一次gc再清理了,③使用标记清除算法会产生大量的碎片空间,可以通过参数XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理)④执行过程不确定性大,在并发标记和并发清理期间可能会再次触发full gc,此时需要STW暂停用户线程。
CMS(回收老年代)+ParNews(回收年轻代)是经常使用的搭配> G1收集器(jdk9使用):分代收集,收集年轻代和老年代。标记整理算法实现,不会出现内存空间碎片的情况。
垃圾收集器底层算法实现
三色标记:可达性分析遍历的对象按照是否访问过标记成3种颜色。
黑色:已扫描过,安全存活的对象
灰色:已经被垃圾收集器访问过,但至少存在一个引用没有被访问过
白色:尚未被垃圾收集器访问
多标-浮动垃圾:已经扫描过被标为非垃圾对象,但在并发标记过程中,方法运行结束,局部变量会被销毁,本轮GC不会回收这部分内存,被称之为浮动垃圾。
漏标-读写屏障:漏标会导致被引用的对象当成垃圾对象,程序运行会有bug,采用增量更新和原始快照的方式解决。
CMS:写屏障+增量更新(将新的成员变量的引用记录下来)
G1:写屏障+SATB(将原来的成员变量的引用记录下来)
写屏障:在赋值操作前后加一些处理。
增量更新(Incremental Update):黑色对象插入新的引用指向白色对象,记录此引用,再次扫描黑色对象。
原始快照照(Snapshot At The Beginning,SATB):灰色对象要删除指向白色对象的引用时,记录下来,并发扫描结束后,再重新扫描,能扫描到白色对象,将白色对象直接变成黑色对象,让对象在本轮GC中活下来。
记忆集与卡表 :在GC Root扫描的过程中会碰到跨代引用的对象,若扫描过去效率太低,因此使用记忆集(从非收集区到收集区的指针集合),用卡表的方式实现记忆集,卡表每个元素对应一个卡页,卡页中包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1。即有引用。