1.1 概述
垃圾收集器(Garbage Collection ,GC):Java语言中会自动对垃圾进行回收。但是GC需要完成它的功能是就必须思考:
- 那些内存需要回收?
- 什么时候回收?
- 如何回收
Java内存运行时区域的包括有,程序计数器、虚拟机栈、本地方法栈、Java堆、及方法区。其中:程序技术其、虚拟机栈、本地方法栈区域随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊的执行着出栈和入栈操作。因此这几个区域的内存分配和回收是具备确定性的。Java堆和方法区则不一样,一个接口中的多个实现类的内存可能不一样,一个方法的多个分支需要的内存也不一样,所以这部分的内存分配和回收都是动态的。
1.2 需要回收的内存
一般而言,在堆中存放着Java世界中几乎所有的对象实例。垃圾收集器对内存是否需要回收的标准——对象是否被引用。
1.2.1 引用计数算法
给对象添加一个引用计数器,当一个地方应用它时,计数器就加1;当引用失效是,计数器减1。当计数器的值为1时,则判定该对象不可能在被引用。
其优点:实现简单、判定效率高;
缺点:难以解决对象中之间互相循环引用的问题。
1.2.2 可达性分析算法
在主流的商用语言中,主要是采用可达性分析算法(Reachability Analysis)来判断对象是否可达。思路:采用“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路称为“引用链”,当一个对象同过引用链可以和“GC Roots”连接是,则对象不可回收,否则回收对象。
在Java 语言中,可作为GC Root的对象包括有以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中静态属性的引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(即一般说的Native)引用的对象;
一般而言,就是定义的常量。
1.2.3 引用
以可达性分析算法区分对象是否被引用还是过于简单了。对于某些对象,也许需要对象,当内存空间还足够时,将其保留在内存中,否则进行垃圾收集。
因此,将引用在分为:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference).
强引用:垃圾回收器永远不会回收该对象,(造成内存泄漏的主要原因之一);
软引用:当内存充足时,对象不会被回收,否则回收该对象。通常适用于内存空间敏感的地方。内存不够是,进行垃圾回收。回收之后如果还没有足够的内存,会抛出内存溢出异常;
弱引用:当垃圾回收器运行时,无论是否内存足够,会直接回收内存;
虚引用:一个对象是否有虚引用存在,完全对其生存时间产生不了影响,也无法通过虚引用来取得一个对象的实例。其存在的唯一目的是定位对象被垃圾收集的位置。
1.3 垃圾回收算法
1.3.1 标记-清除算法
比如教室如果要更换桌子,我们可以先遍历一下那些桌子是坏并标记,然后在遍历一下桌子是否有标记,把有标记的桌子抬出去。标记-清除算法顾名思义是先标记,再清除。这是最简单的收集算法,容易实现。
缺点:1).需要遍历两遍,效率不高;
2).产生间断性的内存碎片;
1.3.2 复制算法
复制算法是但我们知道对象存活率较低时,可以把还存活的对象格外放在一块区域,然后将另一区域的对象全部回收。
因此,把内存分为一个较大的Eden空间和两个较小的Survivor区间。HotSpot默认的两者比例数8:1。
1.3.3 标记-整理算法
如果对象存活率较高时,可以采用“标记-整理算法”。先将可以回收的对象进行标记,然后让所有可存活的对象向一端移动,直接清除边界以外的内存。
1.3.4 分代收集算法
当前Java虚拟机大多采用分代收集算法。思想是,根据对象的存活周期的不同将内存分为几块。一般讲Java分为新生代和老生代。一般而言,新生代有98%的对象是可以被回收的,所这里一般采用复制算法。老年代存活率比较高,可以采用“标记-清除”或者“标记整理”法。