目录
“充满鲜花的世界到底在哪里,如果它真的存在那么我一定会去”🌹
话不多说,发车!
什么是标记清除算法?
标记-清除算法:首先标记出所有需要回收的对象,标记完成以后,统一回收掉所有被标记的对象。标记清除算法是最基础的收集算法。
缺点:
- 执行效率不稳定,标记和清除两个过程的执行效率随着对象数量的增长而降低;
- 会产生内存碎片,内存碎片太多可能导致无法分配较大对象而不得不重新触发一次垃圾收集
回收之前:(紫色代表需要被回收的对象)
回收之后:
什么是标记复制算法?
标记-复制算法:将可用内存划分为相等的两块,每次只使用其中的一块,如果其中的一块用完了,就将还存活着的对象复制到另外一块上,然后再把已经使用过的内存一次清理掉;
缺点:
- 如果存活的对象较多会产生较多的复制开销;
- 只使用了一半的内存空间,造成了空间的浪费
优点:不会产生内存碎片
回收之前:(紫色代表需要被回收的对象)
回收之后:
什么是标记整理算法?
标记-整理算法:与标记-清除算法一样对对象进行标记,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间的一端进行移动,然后直接清理掉边界以外的内存
缺点:移动对象的操作必须全程暂停用户应用程序才能进行,即stop the world (STW)
优点:解决了内存碎片的问题
回收之前:(紫色代表需要被回收的对象)
回收之后:
什么是分代收集算法?
分代收集算法:将Java堆划分为新生代和老年代两个区域,在新生代中进行垃圾收集时会有大批对象死去,而存活的少量对象将会逐步晋升为老年代中的对象
分代收集下的年轻代采用什么样的垃圾回收算法?
- 首先,所有新生成的对象先存放在年轻代中
- 其次,新生代内存按照 8:1:1 的比例分为一个 Eden 区和两个Survivor(Survivor0,Survivor1)区。大部分对象在 Eden 区中生成。当新对象生成,Eden 空间申请失败(因为空间不足等),则会发起一次minor GC;回收时先将 Eden 区存活对象复制到Survivor0 区,然后清空 Eden 区,当这个 Survivor0 区也存放满了时,则将 Eden 区和 Survivor0 区存活对象复制到另一个 Survivor1 区,然后清空 Eden 和这个 Survivor0 区,此时 Survivor0 区是空的,然后将 Survivor0 区和 Survivor1 区交换,即保持 Survivor1 区为空, 如此往复。当 Survivor1 区不足以存放 Eden 和 Survivor0 的存活对象时,就将存活对象直接存放到老年代。
- 当对象在 Survivor 区躲过一次 GC 的话,其对象年龄便会加 1,默认情况下,如果对象年龄达到 15 岁,就会移动到老年代中
- 年轻代中发生的GC叫做minorGC
分代收集下的老年代采用什么样的垃圾回收算法?
- 在年轻代中经历了多次垃圾回收后依然存活的对象就会被加入到老年代中,因此老年代中的对象都是存活周期比较长的对象
- 当老年代内存满时会触发fullGC,fullGC所发生的频率比较低,一般使用标记整理算法
为什么要采用分代收集算法?
不同的对象所存活的时间是不一样的,如果不按照存活时间进行区分,那么每次进行垃圾回收都是对整个Java堆进行回收,花费的时间相对较长,因此,对于存活时间比较长的对象而言,进行多次遍历是没有意义的,因为它们存活的时间比较长,分代收集算法把不同生命周期的对象划分为不同的代,对每个代采用最合适的垃圾回收算法就可以了
大对象怎么分配?空间分配担保是怎么回事?
- 对象优先在Eden区分配:大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够的空间进行分配时,就会发生minorGC
- 大对象直接进入老年代:大对象是指需要大量连续内存空间的Java对象,大于-XX:PrentenureSizeThreshold参数的对象直接在老年代中分配,目的就是为了减少在Eden和survivor区之间复制所造成的开销
- 长期存活的对象将进入老年代:对象通常在Eden区中诞生,如果经过第一次minorGC后存活下来并进入到survivor区,年龄就会增加一岁,且以后在survivor区中每熬过一次minorGC年龄就会增加一岁,当增加到一定程度之后就会加入到老年代中,默认情况下是15岁
- 动态对象年龄判定:虚拟机并不是永远要求对象的年龄达到某个值时才能进入到老年代,如果survivor空间中低于或等于某个年龄的所有对象大小的总和大于survivor空间的一半,年龄大于或等于该值的对象就可以加入到老年代中
- 空间分配担保:在发生minorGC之前,虚拟机必须检查老年代中最大可用的连续空间是否大于新生代所有对象的总空间,如果条件成立,则认为这次minorGC是安全的;如果条件不成立,虚拟机会查看handlePromotionFailure参数是否允许担保失败,如果允许担保失败,那么会继续检查老年代中最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试一次minorGC,如果小于或者不允许担保失败,那么就会发生FullGC
什么时候进行垃圾回收?
minorGC:
- 大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够的空间进行分配时,就会发生minorGC;随后继续尝试在Eden区存放,发现仍然放不下尝试直接进入老年代,老年代也放不下就会触发 Full GC 清理老年代的空间
full GC:
- 调用 System.gc(),会建议虚拟机执行 Full GC。只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行
- 大对象直接进入老年代导致老年代空间不足
- 空间分配担保失败,会进行full GC
谈谈对CMS收集器的理解?
CMS收集器采用了标记-清除算法。
回收过程:
- 初始标记:只是标记一下GCRoots能直接关联到的对象,速度很快
- 并发标记:从GCRoots的直接关联对象开始遍历整个对象图,耗时较长
- 重新标记:修正并发标记期间因用户线程继续运作而导致标记变动的对象记录
- 并发清除:清理删除标记阶段判断死亡的对象
初始标记和重新标记这两个步骤会发生STW
优点:并发收集,低停顿;缺点:无法处理浮动垃圾(在并发标记和并发清除阶段用户程序还在运行,会伴随产生新的垃圾,CMS无法在当次收集中处理掉,只能在下一次才能处理掉);由于使用了标记清除算法,会产生内存碎片
谈谈对G1收集器的理解?
G1收集器把连续的Java堆划分成了多个大小相等的独立区域(region),每个region可以根据需要扮演新生代或者老年代角色,这样收集器在收集时可以根据不同的角色采用不同的策略进行回收。region中还有一类特殊的区域用来专门存放大对象。大对象的定义就是超过了region区域一半的对象。
G1收集器会根据每个region回收后所获得空间大小以及回收所需要的时间维护一个优先级列表,然后在回收时根据优先级列表进行回收。
过程:
- 初始标记:仅标记GC Roots能直接关联到的对象并修改TAMS指针的值,让其指向一块新的可用的region区域
- 并发标记:递归式的扫描整个对象图,找出要回收的对象。扫描完成以后还要重新处理引用变动的对象
- 最终标记:处理并发阶段引用发生变动的对象
- 筛选回收:根据各个region的回收价值以及回收成本生成回收计划并进行回收
除了并发标记,其余阶段都涉及到STW操作。
优点:结合了标记复制算法和标记整理算法,解决了内存碎片的问题。
缺点:G1和CMS都使用了卡表来处理跨代指针,但G1的卡表实现起来更复杂,而且每个region都必须由有一份卡表;而CMS的卡表只有一份
整理面经不易,觉得有帮助的小伙伴点个赞吧~感谢收看!