垃圾标记阶段算法
判断对象在内存中是否存活。
两种方式来进行判断对象存活
引用计数算法
为每一个对象保存了一个引用计数器算法,具体过程是当A对象被引用时,计数器加一,当引用失效时,计数减一,当计数器为0时,表示该对象不会被使用,可以进行回收。
缺点:该算法无法解决循环引用问题。需要单独的计数属性,增加了内存空间消耗。
可达性分析算法
以根对象集合为起始点,按照从上往下的方式寻找被根对象集合(GC Roots)所直接或者间接连接的对象是否可达,这条链路就被称为引用链,如果不存在这条引用链中的对象,则是可以被回收的对象。
GC Roots可以是哪些对象?
虚拟机栈、本地方法栈、方法区、字符串常量池等地方对堆空间进行引用的,都可以作为 GC Roots 进行可达性分析。
对象的finalization机制
finalize()方法,对象销毁前的回调函数,只会被调用一次。通常在这个方法内执行一些资源释放和清理等功能。
finalization机制提供了允许对象被销毁之前的自定处理逻辑。
注:不要主动调用对象的finalize()方法.可能会造成对象的二次存活;并且该方法执行的时间不固定,可能出现不会执行的情况;可能会影响垃圾回收的性能。
由于finalize()方法的存在,对象一般处于三种状态:
可触及的:从根节点可是遍历,可到达的对象
可复活的:对象可能在finalize()方法调用后复活
不可触及的:被finalize()方法调用,并没有复活,进入不可触及状态。
垃圾回收阶段算法
标记清楚算法
当堆中的有效空间被耗尽,就会停止整个程序,进行标记和清除。
标记指的是:从根节点开始遍历,标记所有被引用的对象(可达对象,正常使用的对象),并非即将被清除的对象。
清除指的是:从头到尾的进行线性遍历,如果发现某个对象没有被标记为可达对象,则将其回收。清除不是置空,而是将清除的对象保存在空闲的地址列表,新对象加载时,会先判断空间是否够,覆盖原有的地址。
优点:基础的算法,容易理解
缺点:效率不高,并且进行回收时,需要停止整个应用程序。清理出来的内存不连续,碎片化。
复制算法
将可用内存按照容量分为大小相等的两个部分,每次只使用一份空间。在垃圾回收的时候,将正在使用的对象复制到另一个部分,然后清除当前内存中的所有对象。
优点:实现简单高效 ,并且保证空间的连续性,不会出现碎片问题
缺点:需要两倍的空间,复制对象,需要维护对象间的引用关系,内存占用和时间开销稍大。
高效性的基础是存活的对象少,垃圾对象多的前提下。
标记压缩算法
第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象。
第二阶段将所有存活的对象按照顺序压缩在内存中的一段,然后对其他位置进行清除。
标记压缩与标记清除的比较
标记压缩比标记清除多了一个整理移动对象的操作。所以本质上标记清除是一个非移动式的回收算法,而标记压缩是移动式的。
优点:内存区域不分散,在给新对象分配内存时,jvm只需要持有一个起始地址。
缺点:移动对象,需要维护对象的引用地址,效率比复制算法低;移动过程中,需要暂停用户程序的运行。
分代收集算法
不同对象的生命周期是不同的,所以根据不同的对象还采取不同的收集方法,以便提高收集效率。一般分为新生代和老年代。
新生代:区域相对较小,对象生命周期短,存活率低,回收频繁。一般采用复制算法,速度快,并且因为有两个伊甸园区,得以让复制算法的内存利用率问题得到环境。
老年代:存放的对象生命周期长,回收不频繁,一般是标记清除和标记清除整理混合实现。
增量收集算法
因为上述算法在垃圾回收过程中会使应用程序处于暂停状态。
增量收集算法将回收线程和应用线程交替进行,收集一小片内存,切换到应用线程,以此反复。算法的基础依旧是标记清楚和复制算法。
分区算法
将一块大的内存分隔成多个小块,每次合理的回收若干个小区间。