判断对象已死
可达性分析算法
以一系列“GC Roots”对象作为根起点,根据引用关系向下搜索,搜索过程所走过的路径被称为“引用链”,如果某个对象到“GC Roots”没有任何“引用链”,也就是说“GC Roots”到该对象不可达,则说明该对象没有被引用,是可回收的。
能成为GC Roots的对象
- 栈帧中局部变量表的中引用的对象
- 方法区中静态变量引用的对象、常量引用的对象
- 本地方法栈JNI引用的对象
- JVM内部引用的对象
- 所有被synchronized持有的对象
垃圾回收算法
标记-清除算法(Mark-Sweep)
用可达性分析算法标记存活对象,标记结束后,回收未被标记的对象。反过来用可达性分析算法标记需要回收的对象,标记结束后,回收被标记的对象。
缺点:
- 当要回收的对象越来越多时,标记和清除所需要的时间会增加,也就是效率会降低。
- 会产生内存碎片
标记-复制算法(Semispace-Copying)
将新生代分为三个区:Eden、Survivor0、Survivor1,每次只是使用Eden+s0或Eden+s1,保持总有一个区域未被使用,每次进行垃圾回收的时候,将存活的对象复制到未被使用的区域,然后清空另外两个已经使用的区域。
优点:
- 不会产生内存碎片
- 当一次Mirror GC发现Survivor放不下的时候,需要其他区域作内存分配担保(安全门)
缺点:- 如果存活对象多的情况,该算法的开销会比较大,但是多数情况下存活对象都比较少。
- 要增加额外的空间,相当于空间换时间。
标记-整理算法(Mark-Compact)
标记阶段还是会使用标记-清除算法,回收阶段会将存活对象移动到内存的一端,然后清除掉边界以外的对象。
优点:
- 不会产生内存碎片
缺点:- 需要较长时间的Stop The World
- 需要更新所有引用存活对象的地方
垃圾收集器
CMS(Concurrent Mark Sweep)
一款使用标记-清除算法的老年代收集器,采用增量更新算法解决并发标记问题
清理过程主要有以下四个阶段:
- 初始标记
标记与“GC Roots”直接关联的对象,需要STW- 并发标记
从“GC Roots”直接关联的对象开始遍历整个对象图,不需要STW- 重新标记
整理并发标记阶段中由于用户线程导致标记发生变动的那部分标记,需要STW
4.并发清除
清除已死对象,不需要STW
分析:
1.并发阶段需要(处理器核心数+3)/4的回收线程数量,也就是核心在4以下时效果不好,会和用户线程抢过多的资源
2.由于是标记-清除算法而且清除阶段与用户线程一起进行,只要不STW那么就会一直产生垃圾(“浮点垃圾”),所以要给老年代预留一定区域给用户线程,否则老年代内存不足会触发“并发失败”,临时启动Serial Old(标记整理算法收集器)重新GC,那STW的时间就会很长了,老年代预留空间这个阈值可以通过XX:CMSIniailtingOccu-pancyFraction来设置
- 因为标记-清除算法会产生内存碎片,所以CMS在几次不整理内存碎片的Full GC后,会在下一次进行Full GC的时候进行碎片整理
G1 收集器
一个可以面向堆任意部分进行收集的标记-复制算法+标记-整理算法垃圾收集器,将堆划分为多个Region(1MB-32MB),从而划分出多个Eden、Survivor、老年代,其中大于Region一半的对象会被划分到Humongous区域,使用原始快照算法解决并发标记问题
清理过程主要有以下四个阶段:
- 初始标记
标记与“GC Roots”直接关联的对象,需要STW- 并发标记
从“GC Roots”开始遍历整个对象图,遍历完后需要处理SATB记录下的引用发生变化的部分,不需要STW- 最终标记
整理并发标记阶段中遗留下来的少量SATB记录引用变化的部分,需要STW- 筛选回收
对每个Region的回收价值进行计算排序,根据用户所期望的停顿时间,制订回收计划,然后清除已死对象,由多个线程并行完成,需要STW
分析:
1.由于分了多个Region所以处理指针跨代问题就需要为每个Region制作卡表,会占用较多的对空间
2.相比增量更新算法,使用原始快照算法能减少并发标记和重新标记的消耗
3.G1需要更多的内存去划分Region,内存资源充足的情况比CMS更占优势
内存分配与回收策略
- 对象优先分配在Eden
- 大对象直接放入老年代
- 长期存活的对象将放入老年代
- 空间担保策略