垃圾回收相关算法
- 垃圾标记阶段的算法之引用计数算法
- 垃圾标记阶段的算法之可达性分析算法
- 对象的finalization机制
- 垃圾清除阶段算法之标记–清除算法
- 垃圾清除阶段算法之复制算法
- 垃圾清除阶段算法之标记-压缩(整理)算法
- 小结
- 增量收集算法
- 分区算法
垃圾标记阶段的算法之引用计数算法
在执行GC前要先判断那些是存活对象,哪些是已经死亡的对象。只有被标记为已死亡的对象,GC的时候才会把它收拾掉。
判断对象是否存活一般有两种方式:引用计数算法(Java并不使用)、可达性分析算法
/**
* -XX:+PrintGCDetails
* 证明:java使用的不是引用计数算法
*/
public class RefCountGC {
//这个成员属性唯一的作用就是占用一点内存
private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB
Object reference = null;
public static void main(String[] args) {
RefCountGC obj1 = new RefCountGC();
RefCountGC obj2 = new RefCountGC();
obj1.reference = obj2;
obj2.reference = obj1;
obj1 = null;
obj2 = null;
//显式的执行垃圾回收行为
//这里发生GC,obj1和obj2能否被回收?
System.gc();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
小结
引用计数算法,是很多语言的资源回收选择,例如Python,它更是同时支持引用计数和垃圾收集机制。
Java并没有选择引用计数,是因为其存在一个基本的难题,也就是很难处理循环引用关系。
Python如何解决循环引用?
①手动解除:很好理解,就是在合适的时机,解除引用关系。
②使用弱引用 weakref, weakref是 Python提供的标准库,旨在解
决循环引用。
垃圾标记阶段的算法之可达性分析算法
也可称为根搜索算法、追踪性垃圾收集
- 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
- 相较于引用计数算法,这里的可达性分析就是Java、C#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集( Tracing Garbage Collection)
基本思路
GC Roots根集合 就是一组必须活跃的引用
注意
- 如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证
- 这点也是导致GC进行时必须”Stop The World”的一个重要原因
-—>即使是号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。
对象的finalization机制
Java语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑。
当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法
finalize()方法允许在子类中被重写
永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用。理由包括下面三点:
①在finalize()时可能会导致对象复活。
②finalize()方法的执行时间是没有保障的,它完全由GC线程决定,极端情况下,若不发生GC,则 finalize()方法将没有执行机会。
③一个糟糕的 finalize()会严重影响GC的性能。由于finalize()法的存在,虚拟机中的对象一般处于三种可能的状态。
具体过程
垃圾清除阶段算法之标记–清除算法
背景
标记—清除算法(Marκ- Sweep)是一种非常基础和常见的垃圾收集算法,该算法被J. McCarthy等人在1960年提出并并应用于Lisp语言。
执行过程
图解
缺点
- 效率不算高
- 在进行GC的时候,需要停止整个应用程序,导致用户体验差
这种方式清理出来的空闲内存是不连续的,产生内存碎片。需要维护一个空闲列表
注意:何为清除
这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。
下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。如果不够就覆盖掉这个存放的空闲地址
垃圾清除阶段算法之复制算法
背景
核心思想
图解
优点
- 没有标记和清除的过程,实现简单,运行效率高
- 复制到另一个空间能保证内存的连续性,不会出现碎片化问题
缺点
- 需要两倍的内存空间
- 对于G1这种分成很多regiondd的GC,复制需要改变对象之间的引用关系,内存占用和时间开销都不小!
垃圾清除阶段算法之标记-压缩(整理)算法
背景
核心思想
图解
优点
- 消除了标记-清除算法中的内存不连续的缺点
- 消除了复制算法中,内存减半的代价
缺点
- 效率低于复制算法
- 如果被移动的对象 还被其它对象所引用,就要改变引用的地址
- 移动过程中会发生STW
小结
没有最好的算法,只有最合适的算法
前面这些算法都有各自的优缺点,为了能更好的让他们配合,分代收集算法应运而生。
分代收集算法的核心思想
不同的对象生命周期是不同的,因此 不同生命周期的对象可以采取不同的收集方式,来提高效率。例如把java的堆分为新生代和老年代
增量收集算法
背景
上述算法都会导致STW发生,为了解决这个问题,便出现了增量收集算法
核心思想
缺点
频繁的线程切换的消耗会造成系统吞吐量的下降
分区算法
核心思想
堆空间越大GC的时间越长。所以为了更好的控制间隔,可以将一块大的内存区域划分为若干个小的区域,每次的合理回收小空间而不是整个堆空间。从而减少GC所造成的停顿。
图解