判断对象是否存活
- 引用计数算法: 对象中添加一个计数器,计数器为0则没有引用
- 优点: 简单 效率高
- 缺点: 无法解决循环引用的问题
- 可达性分析算法: 以GC Roots为起点向下搜索,当一个对象到GC Roots没有任何引用链相连,则是不可用的
- 引用分为 强引用 软引用 弱引用 虚引用四种强度
- 不可达的对象,经过两次标记,才会真正死亡(finalize方法)
- 方法区也有回收: 废弃常量 & 无用的类
HotSpot在进行可达性分析,枚举根节点时,会触发Stop The World,停止所有线程
GC算法
- 标记 - 清除算法: 存在效率问题 & 内存碎片问题
- 复制算法: 将内存划分为两块大小相等的区域,回收时,把存活的对象复制到另外一块
- 高效 but 内存缩小了一半
- 采用此算法回收新生代,一块较大的Eden区和两块较小的Survivor区: 将Eden和Survivor活着的对象一次性复制到另外一个Survivor区
- HotSpot默认Eden和Survivor大小8:1
- Survivor不够用时 需要老年代空间进行内存分配担保
- 标记 - 整理算法: 标记后不对可回收对象进行清除,而是让活着的对象向一段移动,然后直接清理掉边界以外的内存
新生代只有少量对象存活,选用复制算法;老年代对象存活率高,选用标记-清除/整理算法回收
HotSpot在GC发生时,采用主动式中断,在线程的安全区域发起GC
- 安全点 : 以是否具有让程序长时间执行的特征来选定
- 主动式中断: 设置中断标志,各个线程轮询这个标志,并把自己挂起
- 抢断式中断: 首先把所有线程中断,检测不在安全点的线程,恢复跑到安全点
- 安全区 : 一段代码片段中,引用关系不会发生变化
GC收集器
-
新生代
Serial
- 单线程,进行GC时,必须暂停其他工作线程,直到GC结束
- Client模式下默认的新生代收集器
ParNew
- Serial的多线程版
- Server模式下默认的新生代收集器(与CMS收集器配合)
Parallel Scavenge
- 目标是达到一个可控制的吞吐量
- 可开启GC自适应的调节策略
- 复制算法 & 多线程
-
老年代
Serial Old
- Serail的老年代版本
- 标记 - 整理
Paraller Old
- Parallel Scavenge的老年代版本
- 多线程 & 标记 - 整理
CMS
- 获取最短回收停顿时间为目的
- 标记清除
- 初始标记
标记GC Roots能直接关联的对象 - 并发标记
GC Roots Tracing
可以和用户线程一起工作 - 重新标记
修正并发标记期间因程序运行而导致标记产生变动的部分 - 并发清除
可以和用户线程一起工作
- 初始标记
- 缺点
- 对CPU资源敏感 : 会导致程序变慢 ,总吞吐量降低
- 无法清理浮动垃圾 : 边运行边清理,出现在标记后的垃圾,只好等待下一次GC
- 产生大量空间碎片: 标记 - 清除
-
G1
- 并行 & 并发
- 分代收集(不需要其他收集器配合)
- 空间整合 : 将Java堆划分为多个Region,从整体来看基于标记-整理,从局部来看基于复制算法
- 可预测的停顿: 建立可预测的停顿时间模型,能让使用者指明在一个M毫秒的时间片段内,GC时间不超过N毫秒
- 收集过程(类似CMS)
- 初始标记
- 并发标记
- 最终标记
- 筛选回收 : 跟踪Region中垃圾堆积的价值大小,优先回收价值最大的(所以叫Garbage First)
内存分配&回收策略
- 对象优先在Eden分配 , 当Eden区空间不足时, 会触发一次Minor GC
- Minor GC : 发生在新生代的GC,频繁且速度快
- Full GC : 发生在老年代的GC
- 大对象直接进入老年代
- 长期存活的对象进入老年代
- 每个对象有一个年龄计数器
- 在Eden出生,熬过第一次Minor GC并能被Survivor容纳,将移动到Survivor空间
- 在Survivor每熬过一次Minor GC,年龄会加一岁,年龄增长到一定程度,会晋升老年代
- 如果Survivor中,所有相同年龄k的对象大小总和>Survivor空间大下/2 ,则年龄大于k的对象直接进入老年代
- 空间分配担保
- Minor GC之前,检查老年代最大可用连续空间是否大于新生代所以对象总空间,如果成立,则此次Minor GC是安全的
- 如果不成立,检查是否允许担保失败参数
- 允许 : 冒风险进行Minor GC(取每次晋升老年代对象大小平均值作为经验值)
- 不允许: 进行一次Full GC