标记阶段
1.引用计数算法
对每一个对象保存一个整数的引用计数器属性,用于记录对象被引用的情况
优点:实现简单,垃圾对象便于标识,判定效率高,回收没有延迟性
缺点:无法处理循环引用,致命缺陷
2. 可达性分析算法(根搜索算法,追踪性垃圾回收)
java(hotspot)选择的算法 有向图表示可达或不可达
基本思路:
2.1. 可达性分析算法是以根对象集合为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达
2.2. 使用可达性分析算法后,内存中的存活对象都会被根节点集合直接或间接连接,搜索走过的路径称为引用链
2.3. 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾
哪些结构可以作为GCRoot
- java虚拟机栈中引用的对象
- 本地方法栈内JNI引用的对象
- 方法区中类静态属性引用的对象
- 方法区中引用的对象
- 所有被同步synchronized持有的对象
- java虚拟机内部的引用
如果要使用可达性分析算法来判断内存是否可以回收,那么分析工作必须在一个能保障一致性的快照中进行,着也是导致任何GC都会发生停顿的原因
清除阶段
标记-清除算法(Mark-Sweep)
标记:根搜索算法
清除:如果通过某个对象在其Header中没有被标记为可达对象,则将其回收(这里的清除并不是真的置空,把需要清除的地址保存在空闲的地址列表中。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,直接覆盖存放)
优点:实现简单
缺点:
- 效率不高(因为要遍历)
- 在进行GC的时候,要停止用户线程,导致用户体验差
- 这种方式清理出来的空间不连续,容易产生内存碎片,需要维护一个空闲列表
标记-复制算法
将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的所有对象,交换两个内存块的角色,完成垃圾回收
优点:
- 没有标记和清除过程,实现简单,运行高效
- 复制过去以后保证空间的连续性,不会出现“碎片问题”
缺点:
- 需要两倍的内存空间
- 对于G1这种拆分为大量分区的GC,复制而不是移动,意味着GC需要维护大量的引用关系,无论内存占用或者时间开销也不小
- 如果系统的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,或者说非常低才行
幸存者1区和幸存者2区的对象存活时间都比较短,而且有两个分区,适合这个算法
标记-压缩算法(最常用的算法)
优点:
- 消除了标记-清除算法中,内存区域分散的却带你,需要给新对象分配内存时,jvm只需要持有一个内存的起始位置即可
- 消除了复制算法当中,内存减半的高额代价
缺点: - 从效率上来看,标记-整理算法要低于复制算法
- 移动对象的同时,如果对象被其它对象引用,则还需要调整引用的地址
- 移动的过程中,需要全程暂停用户应用程序。即STW(stop-the-world)
三种算法的比较:
分代收集算法
分代收集算法并不是一种具体的算法,而是各个jvm厂商在设计GC的一种设计模式
即:不同生命周期的对象可以采用不同的收集方式,以便提高回收效率,几乎所有的GC都采用分代收集算法执行垃圾回收
各个区域的空间大小的比例
新生代:老年代=1:2
伊甸园区:幸存者区:8:1
年轻代使用的是复制算法,对象存活时间都不长
老年代使的是标记清除和标记整理的混合实现的
增量收集算法
如果一次性将所有的垃圾进行处理,会造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序交替执行。垃圾收集线程只收集一小部分区域的内存空间,接着切换到应用程序线程,依次反复,知道垃圾收集完成。
即:用户线程和GC线程交替执行
缺点:线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,使得系统吞吐量下降
分区算法(G1使用的算法)
把内存区域分为众多小区域,对小区域进行垃圾回收