垃圾收集算法

196 阅读6分钟

在讨论垃圾收集算法之前先判断哪些对象已经死去哪些还存活。

一、判断对象已死

1、引用计数法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用实效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

引用计数算法虽然占用了一些额外的内存空间来进行计数但是它的原理简单,判定效率也很高,大多数情况下是一个不错的选择,但是这个算法有很多例外情况要考虑,必须配合大量额外处理才能保证正确的工作,例如对象之间的相互循环引用问题。

2、可达性分析算法

通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”如果某个对象到GC Roots间没有任何引用链相连,则证明此对象是不可能再被使用的。

GC Roots可包括以下几种:

1)在虚拟机栈中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等

2)在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量

3)在方法区中常量引用的对象

4)在本地方法栈栈JNI引用的对象

5)Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象等还有系统类加载器。

6)所有被同步锁持有的对象

7)反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

除上述之外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象临时性加入。

3、引用

强引用:只要该引用存在,该对象就不会被垃圾收集器回收

软引用:在系统将要发生内存溢出异常前,会把这些对象列进回收范围之内进行第二次回收

弱引用:该对象只能生存到下一次垃圾回收之前

虚引用:不会对对象生存时间构成影响

二、垃圾收集算法

分代理论

1、绝大多数对象都是朝生夕灭

2、熬过越多次垃圾收集过程的对象就越难以消亡

垃圾收集器应该将Java堆划分出不同的区域,然后将回收对象依据年龄分配到不同的区域之中存储。如果一个区域中大多数对象都是朝生夕灭那么把它们集中在一起,每次回收时只关注如何保留少量存活而不是去标记哪些大量将要被回收的对象,就能以较低的代价回收到大量的空间。如果剩下的都是难以消亡的对象,那把它们集中在一起虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存空间的有效利用。

部分收集Partial GC

        新生代收集Minor GC/Young GC:对新生代的垃圾收集

        老年代收集Major GC/Old GC:对老年代的收集

        混合收集Mixed GC:收集整个新生代以及部分老年代的垃圾收集

整堆收集Full GC:收集整个Java堆和方法区的垃圾收集

1、标记-清除算法

首先标记出所有需要回收的对象,在标记完成之后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程。

缺点:第一是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除动作随着对象数量增长而降低;第二是内存空间的碎片化问题,标记清除之后会产生大量不连续的内存碎片可能会导致在需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。


2、标记-复制算法

目的:为了解决标记-清除算法面对大量可回收对象时执行效率低的问题。

把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor空间。发生垃圾搜集时将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和Survivor。HotSpot虚拟机默认Eden和Survivor的比例为8:1。当Survivor空间不足以容纳一次Young GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保。

缺点:在对象存活率较高时就需要进行较多的复制操作,效率将会降低,而且需要额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端现象。


3、标记-整理算法

标记过程仍然与标记-清除算法一样,后续让所有存活的对象都向内存空间一段移动,然后直接清理掉边界以外的内存。、

如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活的区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种操作必须全程暂停用户应用程序才能进行,这种停顿被称为“Stop The World”。

如果不考虑移动和整理存活对象的话,空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决,这样会影响应用程序的吞吐量。

三、回收方法区

回收的内容:废弃的常量和不再使用的类型

判断一个类型是否不再使用的条件

1、该类所有的实例都已经被回收

2、加载该类的类加载器已经被回收

3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

同时满足上述三个条件虚拟机才被允许对该类进行回收

在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通过都需要虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。