垃圾收集(Garbage Collection, GC)

一、如何判断哪些对象需要被回收,现在主流有两种算法,引用计数算法和根搜索算法。

  1.引用计数算法(Reference Counting):

  给每一个对象添加一个引用计数器,用于记录该对象被引用的次数,当一个对象的计数器为0,那么该对象就需要被收集。

  引用计数算法实现简单,判定效率高,但是无法解决对象之间的相互循环引用(例如:objA和objB都有instance字段,objA.instance=objB; objB.instance=objA,除此之外,这两个对象再无其他引用,再令objA=null;objB=null;无法回收)的问题,所以未被Java语言选用。

  2.根搜索算法(GC Roots Tracing)

  通过一系列名为”GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象没有任何引用链相连,则证明此对象是不可用的。

  GC Roots的对象包括:

  1)虚拟机栈(栈帧中的本地变量表)中引用的对象;

  2)方法区中的类静态属性引用的对象;

  3)方法区中的常量引用的对象;

  4)本地方法栈中Native方法引用的对象;

 

二、GC回收内存的过程:

  通过根搜索算法找出不可达的对象,并不是直接就回收的,而是被标记一次然后进行一次筛选,筛选条件是:该对象时候有必要执行finalize()方法,当对象没有覆盖finalize()方法或者已经被虚拟机调用过,虚拟机都判定为”没有必要执行”。

  当虚拟机判定为有必要执行finalize()方法,该对象将会放在F-Queue队列中,被一条由虚拟机创建的、低优先级的Finalizer线程去执行该对象的finalize()方法,但并不保证它运行结束。

  若在finalize()方法中该对象无法和任何一个引用链建立关联,就会在第二次被标记时移除”即将回收”的集合,之后被GC回收。任何一个对象的finalize()方法只会被调用一次,若下一次该对象再次面临回收,则不会被执行。

 

三、方法区的回收:

  方法区进行GC性价比比较低,主要包括两部分:废弃常量和无用的类。

  同时满足下面3个条件才算是无用的类:

  1.该类的所有实例都已经被回收;

  2.加载该类的ClassLoader已经被回收;

  3.该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法。

 

四、GC算法:

  1.标记-清除算法

  分为”标记”和”清除”两个步骤:首先标记所有需要回收的对象,之后统一回收掉所有标记的对象。

  缺点:标记和清除过程效率都不高;清楚后会产生大量不连续的内存碎片;

  2.复制算法

  将内存分为大小相等的两块,每次只用一块,当着快用完了,就将还存活的对象复制到另外一块上面,把使用过的内存空间一次清理掉。这样不会出现内存碎片的问题,实现简单,运行高效。(适合年轻代内存回收)

   缺点:内存变为原来一般,使用率低。

  3.标记-整理算法

  第一步和第一种算法相同,不过后续步骤是把所有存活对象都向一段移动,然后清理掉边界以外的内存。(适合年老代内存回收)

 

五、分代收集算法(Generational Collection):

  根据对象的存活周期的不同,一般将Java堆分为新生代和老年代,根据各个年代的特点采用最适合的收集算法。

  在新生代,由于98%都是短周期的对象,适合复制算法。一般做法是:将新生代内存分为一块较大的Eden区和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间,当回收时,将其中还存活的对象一次性拷贝到另外一块Survivor空间上,最后清理之前的Eden和Survivor内存。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1。当Survivor内存不够时,需要依赖其他内存(指老年代)进行分担担保(Handle Promotion)。

  在老年代,大都是存活周期长的对象,并且没有额外空间进行分配担保,适合”标记-清理”或者”标记-整理”算法。

 

六、内存分配和回收策略:

1.对象优先在Eden分配:

  对象在新生代Eden区分配,当Eden区没有足够的空间进行分配时,虚拟机发起一次MinorGC;

2.大对象直接进入年老代:

  需要大量连续内存空间的Java大对象直接在年老代中分配,通过-XX:PretenureSizeThreshold参数调整门槛大小;

3.长期存活的对象将进入年老代:

  虚拟机给每个对象定义一个对象年龄计数器,每当该对象熬过一次Minor GC,年龄就加1,当加到一定程度(默认是15岁)时,就会晋升到年老代。通过-XX:MaxTenuringThreshold参数来设置门槛大小;

4.动态对象年龄判定:

  如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,那么》=该年龄的对象可以直接进入年老代,无需满足MaxTenuringThreshold要求的年龄;

5.空间分配担保:

  在发生Minor GC之前,虚拟机会首先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果满足,则此次Minor GC就是安全的,如果不满足,虚拟机会查看HandlePromotionFailure参数是否允许担保失败,并且检测之前每次晋升到年老代的平均大小是否大于年老代的剩余空间大小,如果允许担保失败而且剩余空间满足,则进行一次Minor GC,虽然此次Minor GC是有风险的;否则的话,改为直接进行一次Full GC;一般情况为了避免Full GC过于频繁,会把HandlePromotionFailure开关打开。

posted on 2014-11-25 11:20  离不开水的鱼  阅读(235)  评论(0)    收藏  举报