一、GC概念
- GC定义:
Garbage Collection 垃圾收集。这里所谓的垃圾指的是在系统运行过程当中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不被释放,可能导致OOM。
- 为什么引入GC:
由于Java不像c/c++那样可以由程序猿去控制内存空间的分配、管理、释放,因此引入GC,目的防止人为导致的内存泄露。
- GC作用内存区域:
堆和方法区。内存区域中的程序计数器、虚拟机栈、本地方法栈这3个区域随着线程而生,线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈的操作,每个栈帧中分配多少内存基本是在类结构确定下来时就已知的。在这几个区域不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。而堆和方法区是空间大小是动态分配和动态回收的。
- 触发GC的条件:
①、程序调用System.gc时可以触发;
②、系统自身判断GC的依据:根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程。
二、GC算法
1 确认垃圾算法
1.1 引用计数算法(Reference counting)
原理:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
缺点:无法解决对象相互循环引用的问题,且每次对对象复制时均要维护引用计数器,且计数器本身也有一定的消耗。
看以下代码:
1 public class Main {
2 public static void main(String[] args) {
3 MyObject object1 = new MyObject();
4 MyObject object2 = new MyObject();
5
6 object1.object = object2;
7 object2.object = object1;
8
9 object1 = null;
10 object2 = null;
11 }
12 }
13
14 class MyObject{
15 public Object object = null;
16 }
后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们,为了解决这个问题,也就是上述引用计数法的缺点“解决对象相互循环引用的问题”,Java中采取了可达性分析法。
1.2 可达性分析算法(Reachability Analysis):
原理:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。
如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。如下所示:
对象Object5 —Object7之间虽然彼此还有联系,但是它们到 GC Roots 是不可达的,因此它们会被判定为可回收对象。
在Java语言中,可以作为Gc Roots的对象包括下面几种:
-
虚拟机栈(栈帧中的本地变量表)中引用的对象。
-
方法区中类静态属性引用的对象。
-
方法区中常量引用的对象。
-
本地方法栈中JNI(即一般说的Native方法)引用的对象
2 复制算法(Copying):
原理:它先将可用的内存按容量划分为大小相同的两块,每次只是用其中的一块。当这块内存用完了,就将还存活着的对象复制到另一块上面,然后把已经使用过的内存空间一次清理掉。
- 从根集合(GC Root)开始,通过追踪从From找到存活的对象,拷贝到To中;
- From和To身份交换
特点:没有内存碎片,只要移动堆顶指针,按顺序分配内存即可。代价是将内存缩小位原来的一半。
地方:适合新生代区进行垃圾回收。serial new,parNew和parallel scanvage收集器,就是采用该算法进行回收的
3 标记-清除算法(Mark-Sweep)
原理:分为标记和清除两个阶段:首先标记出需要回收的对象,在标记完成以后统一回收所有被标记的对象
- 标记:从根集合开始扫描,对需要回收的对象进行标记;
- 清除:扫描整个内存空间,清除被标记的对象。
特点:相比复制算法,利用Survivor的内存空间效率更高些。但也留下了两个缺点:
①、效率问题,执行了两次扫描,耗时严重,因此效率不高;
②、空间问题,留下了不连续的内存碎片,可能导致程序运行过程需要分配较大的对象时候,无法找到足够连续内存而不得不提前触发一次垃圾收集
地方 :适合在老年代进行垃圾回收,比如CMS收集器就是采用该算法进行回收的。
4 标记-整理算法(Mark-Compact):
原理:分标记和整理两阶段,先是标记需要回收的对象,然后让存活的对象像一端移动,最后清理掉边界以外的内存,也就是需要回收的对象。
- 标记:标记未被引用的对象
- 整理:让所有存活的对象都向一端移动,清理标记的对象
特点:不会产生空间碎片,但整理会占用一定的开销。
地方:适合老年代进行垃圾收集,parallel Old(针对parallel scanvange gc的) 和Serial old收集器就是采用该算法进行回收的。
5 分代收集算法:
原理:根据对象存活的周期的不同将内存划分为几块,然后再选择合适的收集算法,也称为分代收集算法
根据堆分为新生代和老年代,采用不同的GC算法进行搭配。在新生代中,每次垃圾收集都会有大量的对象死去,只有少量存活,所以选用复制算法。老年代因为对象存活率高,没有额外空间对他进行分配担保,所以一般采用标记整理或者标记清除算法进行回收。