一、认识垃圾回收
谈到垃圾回收(Garbage Collection,简称GC),GC中的垃圾,特指存在于内存中的、不会再被使用的对象。对于内存空间的管理来说,识别和清理垃圾对象是至关重要的。
二、常用的垃圾回收算法
2.1 引用计数法(Reference Counting)
引用计数法是最经典也最古老的一种垃圾收集算法。
引用计数器的实现很简单,对于一个对象A,只有有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器减1。只有对象A的引用计数器的值为0,则对象A就不可能再被使用。
引用技术器的实习也非常简单,只需为每个对象配备一个整型的计数器即可。但是,引用计数器有两个非常严重的问题:
- 无法处理循环引用的情况。因此,在Java的垃圾回收器中,没有使用这种算法。
- 引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能会有一定的影响。
2.2 标记清除法(Mark-Sweep)
标记清除算法是现代垃圾回收算法的思想基础。标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。标记清除算法可能产生的最大问题就是空间碎片。
- 可达对象:通过根对象进行引用搜索,最终可以达到的对象。
- 不可达对象: 通过根对象进行引用搜索,最终没有被引用到的对象。
在Java语言中,作为GC Roots的对象包括以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法去中类静态变量属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
它主要不足有两个:一是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前出发另一次垃圾收集动作。
2.3 复制算法(Copying)
复制算法的核心是:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
在Java的新生代串行垃圾回收器中,使用了复制算法的思想。
在垃圾回收时,eden空间中存活的对象会被复制到未使用的survivor空间中(假设是to),正在使用的survivor空间(假设是from)中的年轻对象也会被复制到to空间中(大对象,或者老年对象会直接进入老年代,如果to空间已满,则对象也会直接进入老年代)。此时,eden空间和from空间中的剩余对象就是垃圾对象,可以直接清空,to空间则存放此次回收后的存活对象。这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间浪费。当所有存活对象都被复制到survivor区后(图中为to)。简单的清空eden区和备用的survivor区(图中为from)即可。
复制算法比较适用于新生代。因为在新生代,垃圾对象通常会多于存活对象。复制算法的效果会比较好。
2.4 标记压缩法(Mark-Compact)
标记压缩算法是一种老年代的回收算法。它在标记清除算法的基础上做了一些优化。和标记清除算法一样,标记压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,他并不是简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比较高。
2.5 分代算法(Generational Collecting)
根据垃圾回收对象的特性,使用合适的算法回收,分代算法就是基于这种思想。新生代使用复制算法,老年代使用标记压缩算法或标记清除算法,以提高垃圾回收效率。
2.6 分区算法(Region)
分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成梁旭的不同小区间。每个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
一般来说,在相同的条件下,堆空间越大,一次GC时所需要的时间就越长,从而产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割为多个小块,根据目标的停顿时间,每次合理的回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。
三、判断可触及性
垃圾回收的基本思想是考察每一个对象的可触及性,即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,一般来说,此对象需要被回收。但事实上,一个无法触及的对象有可能在某一条件下“复活”自己,如果这样,那么对他的回收就是不合理的,为此,需要给出一个对象可触及性状态的定义,并规定在什么状态下,才可以安全的回收对象。