垃圾收集 准备
引用计数法
方法:对象添加一个引用计数器;每增加一个引用,计数器加1;当引用失效时,计数器减一
问题:无法解决对象之间相互循环引用问题
可达性分析算法
方法:从GC Roots节点向下搜索,产生一系列引用链,如果对象到GC Roots没有任何引用链,则证明该对象不可用
扩展: JDK1.2后引用分为 强引用、软引用(二次回收)、弱引用(下一次GC前)、虚引用(GC通知)
回收方法区
回收废弃常量:没有任何引用则清理出常量池
回收无用类:所有实例被回收 / ClassLoader被回收 / 无法通过反射访问该类方法
垃圾收集 算法
常用的垃圾回收算法有三种:标记-清除算法、复制算法、标记-整理算法
标记 - 清除 算法
方法:
- 标记所有需要回收的对象
- 统一回收需要被标记的对象
缺点:
- 标记和清除 过程效率不高
- 标记清除后产生大量内存碎片,导致后续大对象没有连续内存从而触发GC
复制 算法
方法:
- 将内存划分为大小相等的两块,每次只使用其中的一块
- 当其中一块内存用完了,将存活的对象复制到另一块上面
- 把之前已使用过的内存空间一次性清理掉
优化:
- 内存划分为 Eden:Survivor:Survivor = 8:1:1
- 每次使用Eden和一块Survivor,使用完后,将存活对象复制到另一块Survivor
- 如果Survivor空间不够用,需要依赖老年代进行分配担保
缺点:
- 内存浪费了50%,优化后浪费10%
- 较多复制操作使得效率变低
- 需要额外的空间进行分配担保
标记 - 整理 算法
方法:
- 先对可用的对象进行标记
- 对所有标记的对象向一端移动
- 清理掉端边界以外的内存
分代收集 算法
方法:
- 把堆内存分为新生代(Eden区、FromSurvivor区、ToSurvivor区)和老年代
- 新生代由于朝生夕灭的特点,采用复制算法
- 老年代由于对象存活率较高,采用标记-清除 和 标记-整理算法回收
步骤:
- 新的对象分配在Eden区
- Eden区没有空间,进行一次 Minor GC
- 清理后Eden和FromSurvivor中存活对象小于ToSurvivor可用空间则放入
- 清理后存活对象大于ToSurvivor空间则直接进入老年代
- 经过 Minor GC后,Eden依然不能为新对象分配空间,新对象进入老年代
- 经历了15次 Minor GC 还存活在新生代的对象进入老年代
补充规则:
- ToSurvivor区一半以上且年龄相等的对象直接进入老年代
- 如果老年代最大连续可用空间大于新生代所有对象总空间,则进行Minor GC
- 如果老年代最大连续可用空间大于历次晋升老年代对象平均大小且允许担保失败,则Full GC
- java代码调用System.gc(),JVM会进行Full GC
- 永久代空间不足时,会触发 Full GC
- 大对象直接进入老年代时,老年代没有足够连续到大的空间,则会进行 Full GC
垃圾收集 时机
当程序运行时,各种数据、对象、线程、内存都在时刻发生变化,进行垃圾收集需要了解两个概念,安全点和安全区
安全点
- 从线程角度看,安全点可以理解为代码执行过程中的一些特殊位置
- 当线程执行到安全点的时候,说明JVM状态安全,通过暂停用户线程,进行垃圾收集
- 如果需要暂停当前用户线程,但没有在安全点上,需要等待线程执行到安全点再暂停
安全区
- 安全区就是在一段代码片段中,引用关系不会发生变化,相当于拉长的安全点
- 例如sleep或blocked等状态的线程,收集器不会等待这些线程被分配CPU时间再进行GC