1、概述
主要考虑三个问题:哪些内存需要回收?、什么时候回收?、如何回收?
程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。
Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。
2、对象的生存、死亡判断
2.1 引用计数算法
给对象中添加一个引用计数器,每当一个地方引用它时,计数器值就加1;引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。Java虚拟机中没有选用引用计数算法来管理内存,其中最主要的原因就是它很难解决对象之间的相互循环引用问题。
2.2 可达性分析算法
主流方法。这个算法的基本思想就是通过一系列的称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径被称为“引用链”,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,它们将会被判定是可回收的对象。
在Java语言中,可作为GC Roots的对象包括以下几种:
• 虚拟机栈(栈帧中的本地变量表)中引用的对象
• 方法区中类静态属性引用的对象
• 方法区中常量引用的对象
• 本地方法栈JNI(即一般说的native)引用的对象
2.3引用
从JDK1.2后,把对象的引用分为4种级别:强引用、软引用、弱引用、虚引用
1)强引用(StrongReference)如果一个对象的强引用存在,那垃圾回收器绝不会回收它。
2)软引用(SoftReference)用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
3)弱引用(WeakReference)用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾回收之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
4)虚引用(PhantomReference)
它是一种最弱的引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
2.4 生存or死亡
即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象的死亡,至少需要经历两次标记过程。
2.5 回收方法区
方法区的垃圾收集主要回收两部分的内容:废弃的常量和无用的类。
3、垃圾收集算法
垃圾收集算法
1.标记清除算法
分为标记和清除两个阶段,先标记出需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点:效率问题和空间问题,标记清除后会产生大量的不连续内存碎片,内存碎片过多可能会导致程序需要分配较大对象时找不到足够大的连续内存空间而不得不提前触发另一次垃圾回收动作。
2.复制算法
将内存划分为大小相等的两块,每次只使用其中的一块。当这块内存用完了,就将还存活的对象复制到另一块内存上,然后把已使用过的内存空间一次清理掉。
优点:每次只对其中一块进行内存回收,不用考虑内存碎片的问题,实现简单,运行高效;缺点:内存缩小了一半
3.标记整理算法
复制在对象存活率较高时,效率很低。根据老年代的特点,提出该算法。标记过程同标记清除一样,但不是直接对可回收对象进行清理,而是让存活对象朝着一端移动,然后直接清理掉端边界外的内存。
4.分代收集算法
根据各个年代特点分别采用最适当的收集算法。
• 新生代:存活率低,使用复制算法
• 老年代:存活率高,使用“标记-整理”或“标记-清除”算法