深入理解Java虚拟机-3.2 对象已死? - 高飞网

3.2 对象已死?

2016-02-29 16:11:30.0

    垃圾收集器在回收对象之前,首选要确定哪些对象已死。具体有以下算法

引用计数法(Reference Counting)

    给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能被使用的。

优点缺点
实现简单,效率高,多数情况下是不错的选择,著名的应用案例是微软的COM,使用ActionScript3的FlashPlayer、Python等。它很难解决对象之间相互循环引用的问题,因此主流JVM没有采用该算法

下面的代码测试相互循环引用时,是否被回收:

public class ReferenceCountingGC{
	public Object instance = null;
	private static final int _1MB  = 1024 * 1024;
	private byte[] bigSize = new byte[5 * _1MB];
	public static void main(String[] args){
		ReferenceCountingGC objA = new ReferenceCountingGC();
		ReferenceCountingGC objB = new ReferenceCountingGC();
		objA.instance = objB;
		objB.instance = objA;

		objA = null;
		objB = null; 

		System.gc();
	}
}

输出:

java -XX:+PrintGCDetails  ReferenceCountingGC
[Full GC[Tenured: 10240K->215K(10624K), 0.0044920 secs] 10583K->215K(15424K), [Perm : 2422K->2422K(21248K)], 0.0045740 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 def new generation   total 4864K, used 261K [0x00000000eb400000, 0x00000000eb940000, 0x00000000f0750000)
  eden space 4352K,   6% used [0x00000000eb400000, 0x00000000eb441598, 0x00000000eb840000)
  from space 512K,   0% used [0x00000000eb840000, 0x00000000eb840000, 0x00000000eb8c0000)
  to   space 512K,   0% used [0x00000000eb8c0000, 0x00000000eb8c0000, 0x00000000eb940000)
 tenured generation   total 10624K, used 215K [0x00000000f0750000, 0x00000000f11b0000, 0x00000000fae00000)
   the space 10624K,   2% used [0x00000000f0750000, 0x00000000f0785c70, 0x00000000f0785e00, 0x00000000f11b0000)
 compacting perm gen  total 21248K, used 2431K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
   the space 21248K,  11% used [0x00000000fae00000, 0x00000000fb05ffb0, 0x00000000fb060000, 0x00000000fc2c0000)
No shared spaces configured.

    从运行结果看出,10583K->215K,回收了,证明不是引用计数算法。日志输出参考GC日志分析


可达性分析算法(Reachability Analysis)

    主流商用程序语言(Java、C#)的主流实现中,都是用可达性分析来判定对象是否存活的。这个算法的基本思想是通过一系列的称为”GC Roots“的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如下图的object5、object6、object7。


    在Java语言中,可作为GC Roots的对象包括下面几种:
    a)虚拟机栈(栈帧中的本地变量表)中引用的对象。
    b)方法区中类静态属性引用的对象
    c)方法区中常量引用的对象
    d)本地方法栈中JNI引用的对象

引用分类

    在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。

强引用
程序代码中普遍存在的
软引用
描述一些还有用并非必需的对象
弱引用
描述非必须的对象
虚引用
也被称为幽灵引用或幻影引用,是最弱的引用关系,

finalize()方法

    即使在判定方法不可达之后,对象也并非是一定回收的。需要两次标记过程。如果对象到GC Roots没有可达引用链,则会被第一次标记并且进行一次筛选,筛选的条件是此对象有没有必要执行finalize()方法。当对象没有覆盖finalize()方法或已经被执行过时(只能被执行一次),视为没有必要执行。

    如果有必要执行的话,则该对象会被放到F-Queue的队列中,并在稍后由一个由虚拟机自动建立的,低优先级的Finalizer线程去执行。但这个执行只保证虚拟机触发这个方法,不保存能运行完成。如果在该方法中将该对象又连到GC Roots上,那么它就有可能逃脱清理。

    需要特别说明的是,关于对象死亡时finalize()方法去“拯救”对象并不可取。相反,建议大家尽量避免使用它,因为它不是中的析构函数,而是Java刚诞生时为了使C/C++程序员更容易接受它做的一个妥协。它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序。建议使用try-finally或其他方式。

回收方法区

    Java虚拟机规范中说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区中进行垃圾收集的“性价比”一般比较低;在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。

    永久代中的垃圾收集主要回收两部分内容:废弃常量和无用的类。

废弃常量
如一个字符串常量“abc”已经进入到了常量池,但没有引用再使用它,此时将会把清理。常量池中其他类(接口)、方法、字段的符号引用也与此类似。
无用的类

该类所有的实例都已被回收,即java堆中不存在该类的任何实例。

加载该类的ClassLoader已经被回收

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

    在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。