1.对象的回收
判断对象是否可以被回收有下面几种算法
1.1 引用计数算法:给对象添加一个引用计数器,每当一个地方引用它计数器就+1,引用失效时就-1,当计数器为0就是不再被引用的对象。但主流java虚拟机并不使用此方法,因为此方法难以解决对象之间循环引用的问题。
1.2 可达性分析算法:这个算法的思路就是通过一系列称为“GC Roots”的对象作为起点,从这些节点往下搜索,搜索所走过的路径称为引用链,一个对象到GC Roots没有任何引用链相连时(不可达),则此对象是无用的。
可作为GC Roots的对象包括:
①虚拟机栈(栈帧中的本地变量表)中引用的对象。
②方法区中静态类属性引用的对象。
③方法区中常量引用的对象。
④本地方法栈JNI(native方法)引用的对象。
2.引用
①强引用:就是指在代码中普遍存在的如:Obj obj = new Obj(); 这类引用,只要强引用还在,对象永远不会被回收。
②软引用:指一些还有用但非必须的对象,此类引用的对象在内存充足时不会被回收,当即将发生内存溢出的错误时,此类引用对象就会被列入回收范围之中进行二次回收。回收后如果内存依然不足,则会抛出内存溢出的错误。JDK1.2之后提供SoftReference类来实现软引用。
③弱引用:在下一次垃圾回收时,此类引用的对象一定会被回收,可以使用WeakReference类来实现弱引用。
④虚引用:最弱的一种引用关系,没有实质性的作用,为一个对象设置虚引用关联的目的就是为了让此对象在被回收时收到一个系统通知。PhantomReference类。
3.finalize()方法
当一个对象被可达性分析发现没有与GC Roots相连的引用链时,会进行第一次标记筛选。筛选对象是否有重写finalize方法,或者此方法是否被调用过一次,如果此对象有必要执行finalize方法,则将对象放进一个F-Queue队列中进行二次标记,如果在此方法中对象重新被赋值到其他类变量或者对象成员变量,则不会被回收。但是finalize方法只会被执行一次,且不一定会执行完成(因为防止此方法执行太久或死循环影响内存回收系统)
4.回收方法区
永久带中的垃圾回收主要是两部分内容:废弃常量和无用的类。废弃常量就是没有任何地方引用也没有任何字面量对应此常量。
而无用的类需要满足以下条件:
a.该类所有实例都已被回收。
b.加载该类的classLoader已经被回收。
c.该类的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
5. 垃圾收集算法
5.1 标记-清除算法:算法分为两个阶段,1.首先标记出所有需要回收的对象。2.标记完成后统一回收。
此算法为最基础算法,有两个不足之处:
①标记和清除效率低。
②此算法会导致内存空间出现碎片,空间不连续的问题。当需要分配较大内存时,可能无法找到连续的未分配空间,从而导致提前触发另一次GC。
5.2 复制算法:此算法的提出则是为了解决效率问题,即将可用内存分为两块大小一样的空间,每次只使用其中一块,当这一块内存不足以进行分配时,就将此块内存上的存活对象复制到另一块上,然后直接清除整块内存。实现简单运行高效,但代价就是要浪费掉一半的内存空间。目前商业虚拟机使用此方法回收新生代,内存也不是分为平均的两份(因为新生代中98%的对象是“朝生夕死”),而是将内存分为一块较大的Eden空间,和两块较小的Survivor空间。HotSpot虚拟机的Eden和Survivor默认按照8:1进行分配。使用时先使用Eden和其中一块Survivor,当回收时将这两块还存活的对象一次性复制到另一块Survivor中,然后清理掉这两块空间。
5.3 标记-整理算法:复制算法在存活对象较多时效率比较低,因为需要复制的对象较多。而老年代的对象存活率较高,所有采用另一种算法,即标记-整理算法,标记过程和标记-清除算法一样,但后续不是直接清理可回收对象,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。
5.4 分带算法:按对象存活周期把内存划分为不同的块,一般是新生代和老年代。新生代对象存活率低,则使用复制算法,只需要复制成本。老年代存活率高,且没有额外的空间进行分配担保,则使用标记-整理算法或标记-清除算法。
6. HotSpot算法实现
6.1 枚举根节点:可达性分析时GC需要停顿,因为要保持一致性,意思是在整个分析期间系统就像被冻结在某个时间点,不能出现分析过程中对象引用还在不断变化的情况,否则分析结果的准确性无法得到保证。这是GC时Java线程停顿的重要原因。目前主流JVM都采用准确式GC,即JVM知道内存中某个位置具体是什么类型。HotSpot是使用一组OopMap数据结构来实现的,在类加载完成后,HotSpot就把对象内什么偏移量上是什么数据类型计算出来。GC扫描时就可以直接得知这些信息。
6.2 安全点:为了OopMap不产生大量的空间开销,HotSpot不会为每条指令生成OopMap,只是在“安全点”进行记录,而呈现也并非在所有地方都能停下了进行GC,只有执行到达安全点才能暂停。安全点的选定是以“是否具有让程序长时间执行的特征”为标准选定的,“长时间执行”的明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生SafePoint。另外如何让所有线程在GC时都跑到最近的安全点停下来有两个方案:
①抢先式中断:不需要线程配合,GC发生时首先中断所有线程,如果发现线程不在安全点上,则恢复线程继续跑到安全点再停下来(目前几乎没有采用这种方案)。
②主动式中断:当GC需要中断线程时,不直接操作线程,只设置一个标志,线程执行时主动轮训此标志,如果为真,就自己中断挂起,轮训标志的地方和安全点是重合的,还有就是创建对象需要分配内存的地方
6.3 安全区域:安全点的扩展,指在一段代码片段中,引用关系不会发生改变,在此区域中任意地方开始GC都是安全的。