简介
GC:Garbage Collection 垃圾收集
(1)GC:Garbage Collection 垃圾收集。这里所谓的垃圾指的是在系统运行过程当中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不被释放,可能导致OOM。
在C/C++里是由程序猿自己去申请、管理和释放内存空间,因此没有GC的概念。而在Java中,后台专门有一个专门用于垃圾回收的线程来进行监控、扫描,自动将一些无用的内存进行释放,这就是垃圾收集的一个基本思想,目的在于防止由程序猿引入的人为的内存泄露。
(2)事实上,GC的历史比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的3件事情:哪些内存需要回收?什么时候回收?如何回收?
(3)内存区域中的程序计数器、虚拟机栈、本地方法栈这3个区域随着线程而生,线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈的操作,每个栈帧中分配多少内存基本是在类结构确定下来时就已知的。在这几个区域不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。
分类
引用计数算法(比较老回收算法)
-
概念
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
-
优点
实现比较简单,判断的效率也比较高,使用的地方也是比较多的。
-
缺点
- 循环引用的对象无法回收
- 伴随加法和减法运算,会消耗掉一定的性能
根搜索算法
-
概念
由于引用计数算法的缺陷,所以JVM一般会采用一种新的算法,叫做根搜索算法。它的处理方式就是,设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的。
-
算法分析
-
可达性分析
从根(GC Roots)的对象作为起始点,开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的概念来讲,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
-
根对象(GC Roots)
一般来说根对象主要包含以下几个方便,当前不用语言的实现会有所不同。
- 栈(栈帧中的本地变量表)中引用的对象。
- 方法区中的静态成员。
- 方法区中的常量引用的对象(全局变量)
- 本地方法栈中JNI(一般说的Native方法)引用的对象。
-
标记-清除算法
-
概念
标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。
-
算法分析
-
标记
标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
-
清除
清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
-
-
缺点
-
首先,它的缺点就是效率比较低(递归与全堆对象遍历),导致stop the world的时间比较长,尤其对于交互式的应用程序来说简直是无法接受。试想一下,如果你玩一个网站,这个网站一个小时就挂五分钟,你还玩吗?
-
第二点主要的缺点,则是这种方式清理出来的空闲内存是不连续的,这点不难理解,我们的死亡对象都是随即的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。
-
标记-复制算法:(新生代的GC)
-
概念
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
-
优点
回收完成后对象的空间是连续的,解决内存碎片的问题,实现比较简单,效率也比较高。
-
缺点
空间的浪费。
标记-整理算法:(老年代的GC)
-
概念
标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间。
-
算法分析
-
标记
它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
-
整理
移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
-
-
优点
标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。
-
缺点
-
效率:复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
-
内存整齐度:复制算法=标记/整理算法>标记/清除算法。
-
内存利用率:标记/整理算法=标记/清除算法>复制算法。
-
分代收集算法(新生代的GC+老年代的GC)
-
介绍
当前商业虚拟机的GC都是采用的“分代收集算法”,这并不是什么新的思想,只是根据对象的存活周期的不同将内存划分为几块儿。一般是把Java堆分为新生代和老年代:短命对象归为新生代,长命对象归为老年代。
-
概念
- 少量对象存活,适合复制算法:在新生代中,每次GC时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成GC。
- 大量对象存活,适合用标记-清理/标记-整理:在老年代中,因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”/“标记-整理”算法进行GC。
-
案例
- java 语言的 JVM
- .net 平台的 CLR