首页 > Java大厂高频面试题:说一说垃圾收集和内存分配?
头像
#半情调
编辑于 2021-04-20 15:21
+ 关注

Java大厂高频面试题:说一说垃圾收集和内存分配?

一、垃圾收集算法

1.标记-清除算法
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
不足:(1)效率问题,标记和清除的两个过程效率都不高;(2)空间问题,标记清除后会产生大量不连续的内存碎片。
2.复制算法
将可用内存按容量划分成大小相等的两块,每次只使用其中一块,当这块内存用完了,将还存活着的对象复制到另一块,然后将已使用过的内存一次性清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时不用考虑内存碎片,只需移动堆顶指针,按顺序分配内存即可,实现简单、高效。代价是将内存缩小为原来的一半。
3.标记-整理算法
复制算法在对象存活率较高时就要进行较多的复制操作,效率将会变低,而且浪费空间。
将所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
4.分代收集算法
将Java堆分成新生代和老年代,根据各个年代的特点采用最适当的收集算法。在新生代中,每次都有大量的对象死去,只有少量存活,选用复制算法,只需要付出少量存活对象的复制成本就可以完成手机,老年代因为对象存活率高、没有额外的空间对他进行分配担保,必须使用“标记-清除”或者“标记-整理”算法。

二、垃圾收集器

HotSpot虚拟机的垃圾收集器
Serial收集器:新生代、单线程、简单高效
ParNew收集器:新生代、多线程
Parallel Scavenge收集器:新生代、并行的多线程、复制算法
Serial Old收集器:老年代、单线程
Parallel Old收集器:老年代、多线程、标记-整理算法
CMS收集器
一种以获取最短回收停顿时间为目标的收集器,基于“标记-清除”算法,运作过程:初始标记、并发标记、重新标记、并发清除。初始标记是标记GC Roots能直接关联到的对象,速度很快,并发标记是进行GC Roots Tracing的过程,重新标记是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般比初始标记阶段稍长一些,但远比并发标记的时间短。缺点:(1)CMS收集器对CPU资源非常敏感;(2)无法处理浮动垃圾;(3)收集结束时会有大量空间碎片产生,空间碎片过多时,将会给大对象分配带来很大的麻烦。
G1收集器
一款面向服务端应用的垃圾收集器。
特点:(1)并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。(2)分代收集:G1收集器不需要其他收集器配合就能独立管理整个GC堆,能够通过不同的方式去处理新创建的对象和已存活一段时间、熬过多次GC的旧对象以获取更好的收集效果。(3)空间整合:从整体看基于“标记-整理”算法实现,局部看基于“复制”算法,这两种算法不会产生内存空间碎片,收集后能提供规整的可用内存,有利于程序长时间运行。(4)可预测的停顿:除了追求低停顿外,还能建立可预测的停顿时间模型。
运作过程:初始标记、并发标记、最终标记、筛选回收。

三、内存分配和回收策略

1.对象优先在Eden分配
大多数情况下,对象在新生代Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
2.大对象直接进入老年代
大对象指的是需要大量连续内存空间的Java对象,典型的大对象是很长的字符串和数组。
3.长期存活的对象将进入老年代
既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须识别哪些对象应放在新生代,哪些对象应放在老年代。为了做到这一点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设置为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁),就将会被晋升到老年代。
对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
4.动态对象年龄判定
为了更好适应不同程序的内存状况,虚拟机并不是永远要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
5.空间内存担保
在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间
  • 如果大于,则此次Minor GC是安全的
  • 如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
上面提到了Minor GC依然会有风险,是因为新生代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。 取平均值仍然是一种概率性的事件,如果某次Minor GC后存活对象陡增,远高于平均值的话,必然导致担保失败,如果出现了分配担保失败,就只能在失败后重新发起一次Full GC。虽然存在发生这种情况的概率,但大部分时候都是能够成功分配担保的,这样就避免了过于频繁执行Full GC。

全部评论

(3) 回帖
加载中...
话题 回帖

推荐话题

相关热帖

近期精华帖

热门推荐