#对象已死吗
- GC在回收堆中的对象时,需要事先判断对象是存活的还是死亡的,本章讨论GC是如何判断对象存活状态以及对象回收的一些原理。
一、引用计数算法
- 简述:虚拟机为每个对象配备一个引用计数器,当有一个地方引用此对象,计数器+1;引用失效,计数器-1。当计数器为0的时候虚拟机判定对象不会再被引用,宣布其死亡。引用计数算法的优点是实现简单,运行效率快;缺点是无法判定对象之间循环引用的情况。
二、可达性分析算法(JAVA采用此算法)
- 简述:虚拟机规定GC_Roots为起点,某个对象为终点,如果GC_Roots与这个对象之间没有任何引用链相连接,那么判定该对象可回收。(GC_Roots包括四部分:1、虚拟机栈栈帧中的本地变量表引用的对象。2、方法区中类静态属性引用的对象。3、方法区中常量引用的对象。4、本地方法栈中JNI(Native方法)引用的对象)。
三、再谈引用
-
简述:在jdk1.2以前,java对引用的定义为:当一个reference类型的数据中存储的数值代表另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义很纯粹,但是太过狭隘,在面对一些食之无味,弃之可惜的对象时便无能为力,我们希望能描述这样一类对象,当堆内存足够时,可以保留对象在内存中;但当垃圾收集执行完后内存依然很紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。所以在jdk1.2之后java对引用的概念进行了扩充:分为强引用(Strong_Reference)、软引用(Soft_Reference)、弱引用(Weak_Reference)和虚引用(Phantom_Reference)。
-
强引用:强引用在程序代码中是普遍存在的,类似Object obj = new Object(),GC永远不会回收被强引用的对象。
-
软引用:强度弱于强引用;内存足够时GC不会收集被软引用的对象;当在内存溢出异常抛出之前,GC会对被软引用的对象进行二次回收,如果依然没有回收到足够的内存才会抛出内存溢出异常。jdk1.2之后提供SoftReference类来实现软引用。
-
弱引用:强度弱于软引用;弱引用用来描述非必要对象的,被弱引用的对象只能存活到第二次GC执行之前。jdk1.2后提供WeakReference类来实现弱引用。
-
虚引用:强度最低;被虚引用的对象不会影响其自身的生存时间,也无法通过虚引用操作对象,其意义只在被GC回收时会得到一条系统通知。jdk1.2后提供PhantomReference类来实现虚引用。
四、生存还是死亡
-
简述:在经过可达性分析后,被判定没有引用的对象也不会被立即执行死刑,真正宣告一个对象的死亡至少需要经历两次标记;当断定GcRoots没有能够到达对象的引用链时,对此对象进行第一次标记和筛选,筛选的条件为是否有必要执行finalize()方法,判定依据为该对象中是否重写了finalize()方法和虚拟机是否已经执行过finalize()方法。
-
如果‘未重写’和‘已经重写但虚拟机已经执行过finalize()方法’都视为没必要执行finalize()方法(finalize()方法只会被调用一次),在第二次标记时处死。
-
有必要执行finalize()方法的对象会被放到一个叫做F-Queue的队列中。在稍后虚拟机会创建一个finalizer线程去执行它(此处执行可理解为触发方法,但不会等待finalize()方法运行结束,避免发生因finalize()中堵塞而导致F-Queue队列后面的对象无法回收,从而导致GC崩溃)。
-
finalize()是对象进行自我拯救的最后机会,如果对象可以在finalize()方法中重新被引用,在稍后的第二次标记中将被移出即将回收的集合。(注意:finalizer线程的优先级低到令人发指,如果想通过finalize()拯救对象全凭运气,并且finalize()方法只会执行一次,第二次被列入即将回收集合时无法自救。**书中建议:**忘记finalize()方法。不建议使用。)
五、回收方法区
-
简述:java虚拟机规范中没有强制要求方法区(永久代)实现垃圾回收,永久代的垃圾回收效率比较低;java堆的新生代中,一般系统通常GC执行一次可以回收70%~95%的空间,永久代中的回收效率远低于此。
-
永久代中垃圾回收分为两部分内容,废弃常量和无用的类。
-
回收废弃常量的步骤与回收java堆中的对象很相似;假设一个字符串"abc"已经进入了常量池,但是当前系统中没有一个String对象叫"abc",也就是没有一个String对象引用"abc",也没有其他地方引用这个字面量,如果这时发生内存回收,并且必要的话,这个"abc"会被系统清出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
-
回收无用的类就要复杂的多了,类首先要同时满足下面三个条件才能算是无用的类。
-
所有该类的实例都已经被回收,java堆中没有该类的对象。
-
加载该类的ClassLoader已经被回收。
-
该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。
-
虚拟机可以对满足上述3个条件的无用类进行回收,同对象回收不同,无用类在不使用后不是必然被回收。
-
备注:HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载和卸载的信息,其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。
感谢阅读:
由于个人水平有限,如果有不对的地方希望各位能够留言指正,谢谢!
参考书籍:
《深入理解java虚拟机》