对象存活判定算法(可回收对象的判定)
概述
程序计数器、虚拟机栈、本地方法栈这些区域不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。垃圾收集器关注的是堆和方法区中的垃圾。
对象引用
Java中的垃圾回收一般是在Java堆中进行,因为堆中几乎存放了Java中所有的对象实例。谈到Java堆中的垃圾回收,自然要谈到引用。在JDK1.2之前,Java中的引用定义很很纯粹:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。但在JDK1.2之后,Java对引用的概念进行了扩充,将其分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,引用强度依次减弱。
-
强引用:如“Object obj = new Object()”,这类引用是Java程序中最普遍的。只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象
-
软引用:它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将被垃圾收集器回收。JDK1.2之后提供了
SoftReference
类来实现软引用 -
弱引用:它也是用来描述非需对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存岛下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了
WeakReference
类来实现弱引用 -
虚引用:最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2之后提供了
PhantomReference
类来实现虚引用
几乎所有对象对象实例都是存放在堆中的(除了基本类型),那么垃圾回收器在对堆进行回收前,第一件事就是判断哪些对象还存活着,哪些对象已经死去(即不可能再被任何其他途径使用的对象)。
1. 引用计数算法(Reference Counting)
-
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,任何时刻计数器都为0的对象就是不可能再被使用的
-
引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的选择,当Java语言并没有选择这种算法来进行垃圾回收,主要原因是它很难解决对象之间的相互循环引用问题
![](https://img.haomeiwen.com/i3251891/c2b39b9362a3d39b.png)
2. 可达性分析算法
Java和C#中都是采用根搜索算法来判定对象是否存活的。这种算法的基本思路是通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。
![](https://img.haomeiwen.com/i3251891/adef6c412d71e9cd.png)
在Java语言里,可作为GC Roots
的兑现包括下面几种:
-
虚拟机栈(栈帧中的本地变量表)中引用的对象
-
方法区中的类静态属性引用的对象
-
方法区中的常量引用的对象
-
本地方法栈中JNI(Native方法)的引用对象
生存还是死亡?
实际上,在可达性分析算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:
如果对象在进行可达性分析后发现没有与GC Roots
相连接的引用链,那它会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。
如果该对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue
队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行finalize()方法。finalize()方法是对象逃脱死亡命运的最后一次机会(因为一个对象的finalize()方法最多只会被系统自动调用一次),稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中让该对象重引用链上的任何一个对象建立关联即可。而如果对象这时还没有关联到任何链上的引用,那它就会被回收掉。
回收方法区
在《Java虚拟机规范》中确实说过可以不要求虚拟机在方法区实现垃圾回收,主要是因为在方法区进行垃圾回收的“性价比”很低:在堆中,尤其是在新生代中,常规应用进行一次垃圾回收一般可以回收70%-95%的空间,而永久代的垃圾回收效率远低于此。
永久代的垃圾收集主要回收两部分内容:
- 废弃常量:以字符串常量池为例,假如字符串常量池中有一个字符串“abc“,但是当前系统没有任何一个String对象是叫做”abc“的,那么在这时候发生GC,而且必要的话,”abc“会被JVM”请“出常量池。同理,常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
-
无用的类:判断一个类是无用的类,条件比废弃变量要苛刻的多,要同时满足下面3个条件才能算是”无用的类“:
- 该类所有的实例都已经被回收,意思是堆上没有该对象的实例了
- 加载该类的ClassLoader已经被回收
- 该类对象的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法(因为通过反射,就一定要加载该类)
所以,在大量使用反射、动态代理的应用中,都必须要求JVM具有类卸载的功能,以保证永久代不会溢出。