概述
让我们先想一想,如果让我们完成垃圾收集(Grabage Collection, GC),我们需要考虑哪些事情?
(1)首先肯定需要先知道哪些是垃圾,哪些不是。(2)然后再确定什么时候回收垃圾。(3)最后就是如何去回收。
还需要注意的一点是:
程序计数器,虚拟机栈、本地方法栈都是线程所私有的,它们随线程而生,也随线程而灭。当方法结束或线程结束时,对应的内存自然就跟随着回收了。这三个区域的内存分配与回收都具备确定性。所以不需要过多考虑。
而java堆和方法区不一样,它们是线程所共享的,只有在程序运行期间才能知道哪些对象被创建。这部分内存的分配与回收都是动态的。垃圾收集所关注的就是这部分内存。
对象已死?
首先我们先要决定哪些内存是垃圾,哪些不是。那些还“活着的“对象就不是垃圾,“死了的“(不可能再通过任何途径使用的对象)就是垃圾。
引用计数法
- 原理
给每一个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加一;当引用失效时,计数器值减一。任何时刻计数器值为0的对象就是不可能再被使用的。 - 优缺点
优点:实现简单、判定效率高。
缺点:很难解决对象之间的相互循环引用的问题。如:
public class RefenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024*1024;
private byte[] data = new byte[2 * _1MB];
public static void main(String[] args) {
RefenceCountingGC obj1 = new RefenceCountingGC();
RefenceCountingGC obj2 = new RefenceCountingGC();
obj1.instance = obj2;
obj2.instance = obj1;
obj1 = null;
obj2 = null;
System.gc();
}
}
如果用引用计数法,obj1和obj2都被置为null之后,两个对象都无法再被使用。本应该作为垃圾回收,但是因为obj1和obj2相互引用,使得这两个对象的引用计数都不为0,GC收集器无法回收它们。
根搜索算法
- 原理
通过一系列名为“GC Roots“的对象作为起始点开始搜素,当GC Roots到某个对象不可达时,则说明此对象是不可用的,就被判定为垃圾。
GC Roots对象包括:
虚拟机栈中所引用的对象。
方法区中的类的静态属性所引用的对象。
方法区中的常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)的引用的对象。
再谈引用
上述两种算法都涉及对象的“引用“。在java中,引用有四种类型。分别为:
强引用、软引用、弱引用、虚引用。
回收方法区
有时方法区也称为永久代。一般来说java堆中的新生代垃圾回收比例较高,一般一次可以回收70%-95%的空间;java堆中的老年代比例较低;永久代最低。但是还是用必要对老年代和永久代进行垃圾回收。
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
- 回收废弃的常量
原理:以常量池中的字面量为例。假如一个字符串“abc“进入常量池后,过段时间之后没有任何一个String对象叫做“abc“,那么常量池中的“abc“就是废弃的常量。
- 回收无用的类
满足以下三个条件的类:
(1)java堆中不存在该类的任何实例。
(2)加载该类的ClassLoader已经被回收。
(3)没有通过反射访问。
垃圾收集算法
标记-清除算法(Mark-Sweep)
- 原理:
先标记所有需要回收的对象,在标记完成后统一回收所以被标记的对象。 - 缺点:
(1)效率问题:标记和清除的效率都不高
(2)空间问题:会产生大量不连续的内存碎片。可能分配大的对象时无法找到足够的连续内存,不得不提前触发另一次垃圾收集。 - 适用于:老年代或永久代
复制算法(copying)
- 基本原理
可以把内存分为大小相等的两块,每次使用一块,这一块内存用完之后就把活着的对象复制到另一块上,然后清理掉这块内存。 - 优缺点
优点:不会有内存碎片,简单高效。
缺点:可用内存缩小为原来的一半。 - 适用于:新生代
- 改进
因为新生代大部分对象都是“朝生夕灭“的,所以可以将新生代内存分为三块:一块较大的Eden空间,两块较小的Survivor空间。每次使用Eden和一块Survivor空间,回收时将活着的对象(很少)拷贝到另一块Survivor空间。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。
标记-整理算法(Mark-Compact)
- 原理
先标记可回收对象,完成之后将所有存活的对象向内存的一端移动,然后直接清理掉端边界以外的内存。 - 优点
相比于标记-清除算法,它不会产生内存碎片。 - 适用于:老年代、永久代