三个问题:1.哪些对象需要回收?2.什么时候回收?3.如何回收?
我们先来看第一个问题,哪些对象需要回收,这就需要判断哪些对象已经‘死亡’,哪些对象还在‘存活’着。通常的解决方法就是引用计数法:
给对象引用加一个计数器,当有一个地方引用它时,就加1,当一个引用失效就减1。最后计数器为0的时候就判定它是‘死亡’的。通常来说他的效率很高,但是在这里有一个无法解决的情况,那就是对象之间互相引用的问题,会导致计数器不会为0。
结果是
从程序中可以看出,即使a,b两个对象通过相互引用的方式来保持对象计数器的值不为零,但是任然会被垃圾回收器进行回收,jvm并不是用这个方法。
可达性分析算法:
基本思路就是通过分析名为GCRoot的起始点,开始向下搜索,搜索走过的路径称作引用链,当一个对象没有任何引用链到达Root的时候,就表明可以被回收了。
下面是什么时候回收呢?
引用分类:
强引用,只要引用还存在,那么就不会被垃圾收集器回收。
软引用,在发生内存溢出之前会对这些引用进行二次回收,如果还是没有足够的内存才会抛出异常。
弱引用,无论当前内存是否足够,收集器都会对这些引用进行回收操作。
虚引用,比弱引用还弱,只是在回收的时候,被关联虚引用的对象会收到一个系统通知。
finalize():
在可达性分析中的不可达对象,也是有机会翻盘的,并不是一定被kill。这些对象在宣布死亡的时候会进行两次标记操作,第一次标记操作是筛选出这些对象有没有必要执行finalize的方法,当对象没有覆盖finalize()或者执行过一次,那么就没有必要执行finalize方法了。为什么要执行finalize方法呢,首先,这些对象已经被宣告死亡,让他们执行finalize()是为了给他们一个辩解的机会,把自己(this)赋值给其他引用,那么在第二次标记的时候就会忽略掉。
当对象第二次被标记的时候,那就是死透了,没有机会了。
所以一个对象的finalize()方法在存活时候只能被执行一次。机会都是留给有准备的人的。
finalize method executed
yes, i'm alive
No, i'm dead
具体原因在代码中注释。
接下来看看如何回收呢。
垃圾收集算法:
标记清除:先标记要被回收的区域,然后清除掉,缺点是会存在内存碎片区域。
处理对象是要被回收的区域。
标记-清除法的缺点在于
1.标记阶段暂停的时间可能很长,而整个堆在交换阶段又是可访问的,可能会导致被换页换出内存。
2.另外一个问题在于,不管你这个对象是不是可达的,即是不是垃圾,都要在清楚阶段被检查一遍,非常耗时.
3,标记清楚这两个动作会产生大量的内存碎片,于是当有大对象进行分配时,不需要触发一次垃圾回收动作
复制清除:
处理对象是存活的区域。适用于新生代。
将内存分为两个区域(from space和to space)。所有的对象分配内存都分配到from space。在清理非活动对象阶段,把所有标志为活动的对象,copy到to space,之后清楚from space空间。然后互换from sapce和to space的身份。既原先的from space变成to sapce,原先的to space变成from space。每次清理,重复上述过程。
优点:copy算法不理会非活动对象,copy数量仅仅取决为活动对象的数量。并且在copy的同时,整理了heap空间,即,to space的空间使用始终是连续的,内存使用效率得到提高。
缺点:默认情况下Eden:Survivor=8:1, 所以总会有100-(80+10)%的新生代内存会被浪费掉。
标记整理:
类似清理,适用于存放生命周期较长对象的tenured generation:PSOldGen
1. 标记阶段, 这个阶段和标记-清除收集器的标记阶段相同
2. 整理阶段, 这个阶段将所有做了标记的活动对象整理到堆的底部。
安全点与安全区
程序在运行的过程中,收集器需要枚举GCRoot节点,但是需要暂停运行,当线程运行到safepoint的时候,会暂停,给收集器收集GCRoot节点的机会。同时当线程没有运行或无法到这个点的时候,为了解决这个问题,就将这个点扩大成一个区域,用来保证枚举GCRoot节点的成功。
垃圾收集器都是需要停顿来进行垃圾回收,影响运行效率,所以就出现了收集器的发展,各个收集器都有自己的优缺点,所以虚拟机会针对具体的应用场景进行组合使用。
内存分配与回收策略
新生对象优先在Eden区分配,当这个区域没有足够内存则会进行一次垃圾回收。
大对象直接进入老年区域,当过大的对象在Eden区及Survivor区都没有足够空间的情况下会进入老年区。
长期存活的对象进入老年代,在Eden区域中,进行一次垃圾回收后,仍然存活的对象会分配到Survivor区域(有足够的空间)。之后在每次GC后会将自己的一个年龄计数器加1,直到某个程度(默认15)后,会直接进入老年代。
动态对象年龄判定,虚拟机并非需要一个对象长期存活到某一年龄才会分配到老年代,如果Survivor对象中所有年龄相同的对象大小之和大于等于Survivor区域的一半,那么会直接进入老年代。
空间分配担保,新生代使用复制算法,当Minor GC时如果存活对象过多,无法完全放入Survivor区,就会向老年代借用内存存放对象,以完成Minor GC。在触发Minor GC时,虚拟机会先检测之前GC时租借的老年代内存的平均大小是否大于老年代的剩余内存,如果大于,则将Minor GC变为一次Full GC,如果小于,则查看虚拟机是否允许担保失败,如果允许担保失败,则只执行一次Minor GC,否则也要将Minor GC变为一次Full GC。说白了,新生代放不下就会借用老年代的空间来进行GC