垃圾回收
确定对象是否存活?
两种确定对象是否存活:
引用计数算法
给对象中添加引用计数器,若有一个引用,则计数值加一,若引用失效,计数值减一。
存在的问题:难以解决循环引用问题。
可达性分析算法
通过GC Roots作为起始节点,根据引用关系向下开始搜索,所有搜索走过的路径称为“引用链”。如果一个对象没有到达GC Roots的引用链,则为不可达,可以认为该对象可以被回收。
GC Roots对象
- 虚拟机栈中引用的对象(方法中使用参数,局部变量,临时变量)
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象(字符串常量池中的引用)
- 本地方法栈中引用的对象
- 虚拟机内部的引用(基本数据类型对象的Class对象)
- 被
synchronized
持有的对象。
引用
分类
- 强引用 :引用不会被垃圾收集器回收。通过
new
创建的引用。 - 软引用 :在内存不足时,对于这些进行回收。通过
SoftReference
创建软引用。 - 弱引用 :对象存活到下一次垃圾收集。通过
WeakReference
创建弱引用。 - 虚引用 :不影响对象的生存周期,无法通过虚引用获得对象实例。用于在对象被回收时收到系统通知。
回收方法区
主要是回收:
判断一个类是否可以被回收的条件
- 该类的所有实例对象已被回收。(堆中不存在该类及其子类的实例)
- 加载该类的类加载器已被回收。
- 对应的Class对象没有被引用。
注:大量使用 反射 , 动态代理 的场景中,需要有类型卸载的能力。
垃圾收集算法
Java堆的划分:
垃圾收集分类:
- Minor GC/Young GC:目标为新生代的垃圾收集。
- Major GC/Old GC:目标是老年代的垃圾收集。(CMS)
- Mixed GC:收集整个新生代和部分老年代的垃圾收集(G1)
三种主要的垃圾收集算法
标记-清除算法
分为两个阶段:
存在的问题
标记-复制算法
流程:
将内存划分成两块,每次只使用其中一块。当这一块用完时,将存活的对象复制到另外一块中,然后回收这一块。
优化:将新生代分为:
- Eden区
- From Survivor
- To Survivor
Eden : Survivor = 8 : 2
每次使用Eden区和其中一个Survivor区。当内存不足时,将存活的对象复制到另外一个Survivor区,然后将Eden区和Survivor区进行回收。(Survivor区不足容纳存活的对象时,会使用老年区)
不足
标记-整理算法
流程:
不足
移动对象带来的引用的更新导致整体效率较低,必须暂停用户线程才能执行。
关注吞吐量 --> 标记-整理算法
关注延迟 --> 标记-清除算法
垃圾收集器
- 新生代:
- Serial收集器
- ParNew收集器
- Parallel Scavenge收集器
- 老年代:
- Serial Old 收集器(MSC)
- Parallel Old收集器
- CMS收集器
- 整个堆:G1收集器
Serial收集器
ParNew收集器
Parallel Scavenge收集器
Serial Old收集器
Parallel Old收集器
CMS收集器
流程:
- 初始标记:需要暂停用户线程,使用一个线程标记GC Roots能直接关联的对象。
- 并发标记:单个线程,与用户线程并发执行,标记所有可可达对象。
- 重新标记:需要暂停用户线程,多线程并发执行,用来修正由于用户线程执行导致的标记变动。
- 并发清除:与用户线程并发执行,使用一个线程清除所有不可达的对象。
不足:
G1收集器
特点:
- 回收的范围是整个新生代和老年代
- 将Java堆分成大小相等的区域(Region)
- 通过对每个Region计算其回收价值(获得的空间和花费的时间),在后台维护一个优先队列,根据允许的收集时间,优先回收价值最大的Region。
流程:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收:暂停用户线程,多线程并发回收。
- 首先对每个Region进行按照回收价值和成本进行排序。
- 再根据用户的期望停顿时间制定回收计划。