GC算法
Java堆、Gc堆结构:
判断对象存活
引用计数
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加一;当引用失效时,计数器就减一;任何时刻计数器为零的对象就是不可能再被使用的。
可达性分析
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程所走过的路径称之为”引用链 (Reference Chain)”, 如果某个对象到GC Roots间没有任何引用链相连,或者用图论来说的话就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
并发的可达性分析
参考引用 三色标记算法
理论
分代收集理论
- 强分代假说:熬过越多次垃圾回收过程的对象越难以消亡
- 弱分代假说:绝大多数对象都是朝生夕灭的
- 跨带引用假说:跨代引用对于同代引用来说仅占极少数
- 引入记忆集(Remembered Set)标识哪一块内存会存在跨代引用
Q: 为什么Java堆会划分不同的区域?
强、弱分代奠定了多款垃圾收集器设的一致设计原则, 收集器应该将Java堆划分为不同的区域,然后将回收对象根据其年龄(熬过垃圾回收次数)分配到不同的区域之间存储
Q: 存在跨代引用对象该如何处理?
记忆集,仅将该区维护的对象加入GC Roots进行扫描。
回收算法
3.1 标记-清除
回收过程:标记出所有需要被回收的对象,在标记完成后,统一回收被标记的对象
缺点:
- 执行效率不稳定,如果java堆中存在大量对象,而且大部分是需要被回收的,这时就需要进行大量的标记和清除的动作,导致
标记
和清除
两个过程的执行效率随对象增长而降低 - 会导致内存空间碎片化的问题,标记、清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行时需要分配较大对象时无法找到足够的连续内存而不得不触发另一次垃圾收集动作。
3.2 标记-复制
回收过程:将可用内存按容量划分同等大小的两块,每次只使用其中一块。当使用块内存用完了,就将存活对象复制到另一块中,然后将前使用块的内存空间一次清理掉。
优点:
- 简单高效,分配内存时不需要考虑有空间碎片的复杂情况,只需要移动堆顶指针,按顺序分配即可。
缺点:
- 空间浪费过多,每次都将可用内存缩小为原有一半。
- 如内存多数对象都是存活状态,将会产生大量内存间复制的开销。
半区复制分代方案
hotspot中 Serial、ParNew中新生代布局:
分为一块较大的Eden区和两块较小的Survivor区,每次分配内存只使用Eden和其中一块survivor。发生垃圾收集时将Eden和Survivor区中仍然存活的对象复制到另一块Survivor空间上,然后直接清理掉Eden和使用过的Survivor空间。
Hotspot虚拟机默认Eden和Survivor大小比例时8:1:1,也就是每次新生代中可用内存空间为整个新生代容量的90%,只有一个Survivor空间即10%是被浪费的。
如果Survivor空间不足以容纳一次Minor GC之后存活的对象, 就需要依赖其他内存区域(大多是老年代)进行分配担保(Handle Promotion).
3.3 标记-整理
回收过程:标记过程跟标记-清除
算法一样,但是后续步骤不是对可回收对象直接清理,而是让所有存活对象向内存一端移动,然后直接清理掉边界以外的内存。
缺点:
- 移动存活对象并更新引用对象是一个极为负重的操作。而且这些对象移动操作必须全程暂停用户程序能进行(该停顿有一个形象描述:stop the world。但是现有最新的ZGC和Shenandoah收集器使用读屏障Read Barrier 技术实现了整理过程与用户线程的并发执行。)
3.4 扩展
- 是否移动对象都会存在弊端,移动内存回收会更加复杂,不移动在内存分配时会更复杂(大对象分配)。所以,关注吞吐量的 Parallel
Old收集器是基于
标记-整理
算法,关注延迟的CMS收集器则是基于标记-清除
算法。 - 一种不在内存分配和访问增加太大额外负担的做法:平时多数时间使用
标记-清除
,在内存空间碎片化程度已经达到影响对象分配时再采用标记-整理
算法收集一次。 CMS收集器在面临空间碎片过多时就采用的该种方案。
重点面试题
Q: 为什么1.8要使用MetaData(元空间-直接内存) 替代 PermGen (永久代)?
A:
前提:在JDK8以前大部分Java程序员都在Hotspot虚拟机上开发部署程序,当时Hotspot虚拟机设计团队选择把收集器的分代设计扩展到方法区,或者使用永久代来实现方法区,这样可以使Hotspot的垃圾收集器能够像管理堆一样管理这部分内存,省去为方法区编写内存管理的工作。但是对于其他Java虚拟机 Jrockit 、IBM J9等来说,是不存在永久代概念的。 原因:有两个点
- 更容易出现OOM, 永久代中内存回收目标主要针对常量池的回收和对类型的卸载,一般来说这个区域回收效果比较难令人满意,尤其是对类型的卸载,条件相当苛刻,这部分区域的回收有时确实又是有必要的。 以前sun公司的Bug列表中,层出现过若干个严重的Bug就是低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。
- Oracle收购BEA获得JRockit所有权后,准备将JRockit的优秀功能移植至Hotspot中,但是由于这两个虚拟机方法区实现存在的差异面临很多困难,所以为了hotspot未来的发展, 在JDK6的时候Hotspot逐步移除了永久代。