垃圾回收要考虑的问题
1、那些内存需要回收?
2、什么时候回收?
3、如何回收?
如何判断对象"已死"?
一、Reference Counting ---- 引用计数法
1、算法思想
给对象添加一个引用计数器,每当有一个地方引用它时,计数器的值就加一;当引用失效时,计数器减一。任何时刻,计数器值为0的对象就是不可能再被使用的。
2、采用这种方式的案例
微软的COM技术,使用ActionScript的Flash Player,Python语言以及Squirrel脚本
3、特点
实现简单,效率很高,但是主流的java 虚拟机没有采用这种方式,因为它无法解决循环引用的问题。
public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; // 这个属性的作用就是占点内存,以便在GC 日志中查看内存是否被回收 private byte[] bigSize = new byte[2 * _1MB]; public void testGC() { ReferenceCountingGC referenceCountingGC1 = new ReferenceCountingGC(); ReferenceCountingGC referenceCountingGC2 = new ReferenceCountingGC(); //循环引用 referenceCountingGC1.instance = referenceCountingGC2; referenceCountingGC2.instance = referenceCountingGC1; referenceCountingGC1 = null; referenceCountingGC2 = null; System.gc(); } public static void main(String[] args) { new ReferenceCountingGC().testGC(); } }[GC (System.gc()) [PSYoungGen: 8141K->2720K(38400K)] 8141K->2728K(125952K), 0.0041620 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 2720K->0K(38400K)] [ParOldGen: 8K->2557K(87552K)] 2728K->2557K(125952K), [Metaspace: 2470K->2470K(1056768K)], 0.0063053 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]虚拟机并没有因为这两个对象互相引用就不回收他们,说明虚拟机并不是采用引用计数法来判断对象是否存活的。
二、Reachability Analysis可达性分析算法
在主流的商用程序语言中,java,c#,Lisp,都是采用可达性分析算法来判断一个对象是不是存活的。
1、算法思想
通过一系列的称为GC Roots对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连的时候(也就是图论的GC roots 到这个对象不可达)时,则这个对象是不可达的。
2、采用这种方式的案例
Java,c#,Lisp等语言
3、Java中可作为GC Roots的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
三、关于引用
无论是引用计数法还是可达性分析算法,都是通过引用来判断对象是否存活。
JDK 1.2 之后,java对引用的概念进行了扩从,详细介绍如下。
1、强引用(Strong Reference)
在程序代码中普遍存在的引用,类似 Object object = new Object(),这类都是强引用。只要强引用存在,垃圾回收器永远不会回收掉被引用的对象。
2、软引用(Soft Reference)
描述一些还有用但不是必须的对象。对于软引用关联的对象,在系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次回收,如果回收还没有足够的内存,才会抛出oom异常。Java提供了SoftReference类来实现软引用。
3、弱引用(Weak Reference)
也是用来描述非必须对象,但是强度比软引用还要弱一些,被若引用关联的对象只能生存到下一次垃圾回收之前。当垃圾回收器工作时,无论当前内存是否足够,都会回收掉只被若引用关联的对象。Java提供了WeakReference类实现弱引用。
4、虚引用(Phantom Reference)
也称幽灵引用或者幻影引用。是最弱的一种引用关系。一个对象是否有弱引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用的唯一目的是就是能在这个对象被垃圾回收器回收之前收到一个系统通知。Java 提供了PhantomReference类实现虚引用。
四、生存还是死亡
不可达的对象是不是就一定会死亡,被回收?
不是。一个对象要真正宣告死亡,至少要经历两次标记过程。
第一次标记 :如果对象在进行可达性分析算法之后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机都认为这两种情况被有必要执行被判定死亡。
如果这个对象被判定有必要执行finalize()方法,那么这个对象将会被放置在一个叫做 F-Queue的队列里,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。这里的执行只是指虚拟机会触发这个操作,但并不承诺会等待他运行结束。这样做的原因是,如果一个对象在finalize()方法中发生了死循环,执行缓慢很可能导致F-Queue中的其他对象永久处于等待,甚至导致整个内存回收系统崩溃。
第二次标记 :finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC 将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中拯救自己----只要重新与引用链上的任何一个对象建立关系即可,比如把自己(this关键字)赋值给某个对象的成员变量,那么在第二次标记时它将被移除出“即将回收”集合。如果这时还没有逃脱,那么基本上就是真的被回收了。
注意:任何对象的finalize()方法都只会被系统自动调用一次。