1 对象生死判定方法
1.1引用计数算法
实现简单,效率高,当java虚拟机并没有使用它,因为它很难解决对象之间相互循环引用问题
1.2可达性分析算法
以“GC Roots”的对象为起始点,从这些节点向下搜索,当一个对象到GC Roots没有任何引用链相连,证明该对象不可用
可作为GC Roots 当对象包括:
虚拟机栈中引用的对象,包括栈帧中当本地变量表
方法区中的类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(本地方法)引用的对象
1.3 引用
强引用:类似于“Ojbject object = new Object()”这类的引用,只要强引用存在,它永远不会被回收
软引用:有用但并非必需的对象。在系统将要发生内存溢出异常时,这类引用才会被回收。SoftReference类可实现。
弱引用:被弱引用引用的对象只能生存到下一次垃圾回收之前。WeakReference类可实现。
虚引用:设置虚引用关联的唯一目的是能在这个对象被垃圾收集器回收时能收到一个系统通知
各种引用的具体作用参考:http://www.cnblogs.com/E-star/p/3441846.html
1.4 生存还是死亡
即使在可达性分析算法中不可达的对象,也并非非死不可。
若在上述算法中不可达,他会被第一次标记并进行筛选。筛选的条件是该对象是否有必要执行finalize()方法。当对象没有覆盖该方法,或是该对象已经执行过一次该方法,那么虚拟机将认为它没有必要执行该方法。若没有必要执行该方法,则它将在第二次标记之后被真正地彻底回收。若被判定有必要执行该方法,则将会被放进一个队列中,在另一个低优先级的线程中执行,但不保证一定执行,若在该方法执行过程中,该对象把自己(this)赋值给某个类变量或者对象成员变量,那么第二次标记的时候它将被移出“即将回收”的集合。但不建议实现finalize()方法。
1.5 回收方法区
常规应用进行一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾回收效率远低于此。
永久代的垃圾手机主要回收两部分内容:废弃常量和无用的类。
废弃常量的判定相对简单。比如一个字符串常量在任何String对象都没有对其进行引用,则其就是废弃常量。而“无用的类”的判定则需要满足以下几个条件:
(1)该类的所有实例都已经被回收;(2)加载该类的ClassLoader也已经被回;(3)该类对应的java.lang.class对象没有在任何地方被应用,无法在任何地方通过反射机制访问该类的方法。
虚拟机“可以”对满足上述条件的类进行回收,但并不是必然会进行回收。是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数等进行控制。
注:在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备卸载的功能,以保证永久代不会溢出。
2 垃圾收集算法
2.1标记-清除算法
两个不足:效率问题,标记和清除的效率都不高;空间问题,会产生大量的空间碎片,导致当大对象需要分配内存时没有连续的内存空间可以使用。
2.2 复制算法
实现简单,运行高效。现在的商业虚拟机都采用这种手机算法来回收新生代。将内存分为一个较大的Eden空间和两块较小的Survivor空间。由于大部分的新生代对象都是“朝生夕死”,当回收时,将较大空间Eden空间和已经使用过的那一个Survivor空间中还存活的对象一次性复制到另外一块Survivor空间中,最后清理掉另外的两块空间,但Survivor空间不够用时,就需要依赖其他内存(这里指老年代对其进行分配担保)。在HotSpot中,Eden和Survivor默认分配空间的比例为8:1。
2.3 标记-整理算法
因为在老年代中每次回收存活下来的对象所占比例较大,因此一般不采取复制算法。而采用标记-整理算法。
2.4 分代收集算法
无具体新实现,只是根据对象存活周期的不同将内存划分为几块。一般将Java堆中的对象分为新生代还有老年代,根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都有大批的对象死去,只有少数存活,所以采用复制算法。
而在老年代中因为对象存活率高、没有额外空间对它进行担保,就必须采用标记--清除或者标记--整理算法。
3 HotSpot的算法实现
3.1 枚举根节点
可达性分析在执行时需要确保分析工作的一致性,而保证一致性就得将应用进行停顿直至分析完成。因为目前的主流虚拟机都采用准确式GC,所以Java虚拟机知道在哪里存在着对象的引用,而不用一个不漏地检查完所有执行上下文和全局的引用位置,在HotSpot的实现中,使用的时OopMap的数据结构来实现这一目的的。
3.2 安全点
OopMap数据结构并非时每执行一句指令便生成,它只会在特定的位置生成,这些特定的位置叫安全点。选取安全点的标准是“是否具有让程序长时间执行的特征”,长时间执行最明显的特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转等,具有这些功能的指令才会产生OopMap。
对于Safepoint的另一个需要思考的问题是如何在GC发生时让所有的线程(不包括JNI调用的线程)都“跑”到最近的安全点上停下来。共有两种方案:抢断式中断还有主动式中断。
抢断式中断:不需要线程的执行代码去配合,在GC发生时。首先把所有的线程都中断,如果发生有线程中断的地方不在安全点上,就恢复线程,让它跑到安全点上。现在几乎没有虚拟机采用这种方式。
主动式中断:当GC需要中断线程的时候,不直接对线程进行操作,仅仅简单地设置一个标志,各个线程执行的时候去轮询这个标志是否为真,发现中断标志为真时就自己中断挂起,轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。
3.3 安全区域
安全区域是指在一段代码片段中,引用关系不会发生变化。在这个区域的任何一个地方开始GC都是安全的,可以把Safe Region看做是被扩展了的Safepoint。在线程执行到Safe Region中的代码时,首先标识自己已经进入了安全区域,那样在这段时间JVM要发起GC的时候,就不用管标识自己为Safe Region状态的线程了。若在该时间段系统进入了GC,该线程必须等待系统完成GC收到可以安全离开安全区域的信号才可继续执行。